# Licensed Materials - Property of IBM
# (c) Copyright IBM Corporation 2018, 2019. All Rights Reserved.
# Note to U.S. Government Users Restricted Rights:
# Use, duplication or disclosure restricted by GSA ADP Schedule
# Contract with IBM Corp.

#!/bin/bash

#################################################
#configuration
#################################################
config=$1
namespace=$2
frequency=$3
iterations=$4
interactive=$5
includeNamespaces="[\"default\"]"
excludeNamespaces="[\"kube*\"]"


echo -e "
------ Introduction phase for the user-----

this script takes 4 input parameters, e.g.:

./testCompliance.sh ~/.kube/conf default 5 6

- parameter 1, is the KUBECONFIG file path: ~/.kube/conf
- parameter 2, is the namespace where the compliance is created, it should be = '--watch-ns=XXX' flag used in the compliance-controller. You probably need to pass a different namespace than the default.
- parameter 3, is the time to wait before checking the result of a test, the default is 10 seconds, it should be > 2 x '--reconcilefrequency' flag used for the policy-controller that is by default 3 seconds
- parameter 4, is the number of iteration the test is repeated, to make sure the results are consistent across runs. The default is 3 iterations.
- parameter 5, if 'true', makes the transition between tests manual, requiring the user to press 'enter'. Default is 'false'.

-------------------------------------------
"
sleep $frequency

#################################################
# initialize
#################################################
initialize(){
echo -e "
------ initializing phase-----
setting the kube config and sleep values
------------------------------
"

if [ "$1" = "" ]; then
echo "no KUBCONFIG is provided, using default '$HOME/.kube/conf'"
config="~/.kube/conf"
else 
    echo "using the following config: $config"
fi


if [ "$2" = "" ]; then
echo "no namespace is provided, using 'default'"
namespace="default"
else 
    echo "using the following config: $namespace"
fi

if [ "$3" = "" ]; then
frequency=10
echo "using the default sleep frequency: $frequency seconds"
else 
    echo "using the following frequency: $frequency seconds"
fi

if [ "$4" = "" ]; then
    iterations=3
    echo "using the default test repeat iterations of: $iterations times"
else 
    echo "using the following test repeat iterations: $iterations times"
fi

if [ "$5" != "true" ]; then
    interactive="false"
    echo "using the default test repeat iterations of: $interactive times"
else 
    echo "using the following test repeat iterations: $interactive times"
fi


export KUBECONFIG=$config


echo -e "
------ initializing completed-----
"

}
#################################################
# cleanup
#################################################
cleanup(){

echo -e "
------ cleaning up phase-----
making sure the compliance and it's resource are deleted
-----------------------------
"

echo "starting initial cleaning up phase..."
kubectl -n=$namespace delete cpl demo-compliance
kubectl -n=$namespace delete ns demo-namespace 
kubectl -n=$namespace delete role demo-operator

echo -e "
------ cleaning up phase completed-----
"
}

#################################################
#create compliance
#################################################
create_compliance(){

echo -e "
------ compliance creation phase-----
creating the demo-compliance 
-------------------------------------
"

echo "existing compliances in all-namespaces:"
allCpl=$(kubectl get cpl --all-namespaces -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo $allCpl

sleep 1
kubectl -n=$namespace apply -f- <<EOF
apiVersion: compliance.mcm.ibm.com/v1alpha1
kind: Compliance
metadata:
  name: demo-compliance
  description: Instance descriptor for compliance resource
spec:
  runtime-rules:
    - apiVersion: policy.mcm.ibm.com/v1alpha1
      kind: Policy
      metadata:
        name: demo-policy
        namespace: $namespace
      spec:
        remediationAction: "inform"
        complianceType: "mustnothave" 
        namespaces:
          include: $includeNamespaces
          exclude: $excludeNamespaces
        role-templates:
          - apiVersion: roletemplate.mcm.ibm.com/v1alpha1
            metadata:
              namespace: "" # will be inferred
              name: demo-operator
            complianceType: "mustnothave" # at this level, it means the role must exist with the rules that it musthave below
            rules:
              - complianceType: "mustnothave" # at this level, it means if the role exists the rule is a musthave
                policyRule:
                  apiGroups: ["core"]
                  resources: ["pods"]
                  verbs: ["get", "list"]
        object-templates:
          - complianceType: "mustnothave" 
            objectDefinition:
              kind: Namespace
              apiVersion: v1
              metadata:
                name: demo-namespace
                labels:
                  name: production
EOF

sleep $frequency
complianceName=$(kubectl -n=$namespace get cpl demo-compliance -o jsonpath='{.metadata.name}')

if [ "$complianceName" = "demo-compliance" ]; then
echo "compliance found, moving on..."
else 
    echo "compliance not found!, waiting for a few seconds and trying again"
    sleep $frequency
    complianceName=$(kubectl -n=$namespace get cpl demo-compliance -o jsonpath='{.metadata.name}')
    if [ "$complianceName" != "demo-compliance" ]; then
        echo "ERROR: compliance is not found! Exiting..."
        exit 1
    fi
fi


policyName=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.metadata.name}')
if [ "$policyName" = "demo-policy" ]; then
    echo "policy found, moving on..."
else
    echo "ERROR: policy is not found! Exiting..."
    exit 1
fi

echo -e "
------ compliance creation phase completed-----
"
}

#################################################
# test 1: check the policy status, expecting NonCompliant
#################################################
test_1(){

echo -e "
------test 1-----
check the policy status, expecting Compliant
-----------------
"
policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since both the namespace 'demo-namespace' and RBAC role 'operator' are missing
"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" != "Compliant" ]; then
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting"
    exit 1
    else 
echo -e "
=> policy status is correct, it is 'Compliant' since both the namespace 'demo-namespace' and RBAC role 'operator' are missing
"
    fi   
fi

echo -e "
------test 1 succeeded-----
"
}

#################################################
# test 2: creating the namespace 'demo-namespace', and then checking the policy compliance (should be 'NonCompliant')
#################################################
test_2(){

echo -e "
------test 2-----
checking the policy status after a violation
-----------------
"

echo "creating the namespace 'demo-namespace', this should make the status 'NonCompliant'"
kubectl apply -f- <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: demo-namespace
EOF

sleep $frequency
##checking policy status
policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "NonCompliant" ]; then
echo -e "
=> policy status is correct, it is 'NonCompliant' since namespace 'demo-namespace' exists, and it is a 'mustnothave'"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" != "NonCompliant" ]; then
        echo "ERROR: policy status should be 'NonCompliant', but it is not. Exiting"
    exit 1
    else 
echo -e "
=> policy status is correct, it is 'NonCompliant' since the namespace 'demo-namespace' exists, and it is a 'mustnothave'"
    fi   
fi

echo -e "
------test 2 succeeded-----
"
}

#################################################
#test 3: deleting the namespace 'demo-namespace', and then checking the policy compliance (should be 'Compliant')
#################################################
test_3(){

echo -e "
------test 3-----
checking the policy status after removing the violation
-----------------
"
kubectl delete ns demo-namespace
sleep $frequency
##checking policy status
policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e  "
=> policy status is correct, it is 'Compliant' since namespace 'demo-namespace' exists, and it is a 'mustnothave'"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" != "Compliant" ]; then
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting"
    exit 1
    else 
echo -e "
=> policy status is correct, it is 'Compliant' since the namespace 'demo-namespace' exists, and it is a 'mustnothave'"
    fi   
fi

echo -e "
------test 3 succeeded-----
"
}

#################################################
# test 4: creating the role operator-demo, and then checking the policy compliance (should be 'NonCompliant')
#################################################
test_4(){

echo -e "
------test 4-----
creating the role operator-demo, this should make the status 'NonCompliant'
-----------------
"
 
kubectl apply -f- <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: demo-operator
  namespace: $namespace
rules:
- apiGroups:
  - core
  resources:
  - pods
  verbs:
  - get
  - list
EOF
sleep $frequency

#check compliance change
policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "NonCompliant" ]; then
echo -e "
=> policy status is correct, it is 'NonCompliant' since the RBAC role 'operator' exists, and it is a 'mustnothave'"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" != "NonCompliant" ]; then
        echo "ERROR: policy status should be 'NonCompliant', but it is not. Exiting"
    exit 1
    else 
echo -e "
=> policy status is correct, it is 'NonCompliant' since the RBAC role 'operator' exists, and it is a 'mustnothave'"
    fi   
fi

echo -e "
------test 4 succeeded-----
"
}
#################################################
# test 5: changing the policy remediation action to 'enforce' (should become 'Compliant')
#################################################
test_5(){


echo -e "
------test 5-----
changing the policy remediation action to 'enforce' (should become 'Compliant')
-----------------
"

kubectl -n=$namespace apply -f- <<EOF
apiVersion: compliance.mcm.ibm.com/v1alpha1
kind: Compliance
metadata:
  name: demo-compliance
  description: Instance descriptor for compliance resource
spec:
  runtime-rules:
    - apiVersion: policy.mcm.ibm.com/v1alpha1
      kind: Policy
      metadata:
        name: demo-policy
        namespace: $namespace
      spec:
        remediationAction: "enforce"
        complianceType: "mustnothave" 
        namespaces:
          include: $includeNamespaces
          exclude: $excludeNamespaces
        role-templates:
          - apiVersion: roletemplate.mcm.ibm.com/v1alpha1
            metadata:
              namespace: "" # will be inferred
              name: demo-operator
            complianceType: "mustnothave" # at this level, it means the role must exist with the rules that it musthave below
            rules:
              - complianceType: "mustnothave" # at this level, it means if the role exists the rule is a musthave
                policyRule:
                  apiGroups: ["core"]
                  resources: ["pods"]
                  verbs: ["get", "list"]
        object-templates:
          - complianceType: "mustnothave" 
            objectDefinition:
              kind: Namespace
              apiVersion: v1
              metadata:
                name: demo-namespace
                labels:
                  name: production
EOF
sleep $frequency
#check remediation action change
remediationAction=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.spec.remediationAction}')
if [ "$remediationAction" = "enforce" ]; then
    echo "remediationAction changed to: $remediationAction"
else 
    echo "remediationAction did not changed as expected to 'enforce', it is still: $remediationAction, Exiting ..."
    exit 1
fi  

# check role deletion by the controller
sleep $frequency
roleName=$(kubectl -n=default get role demo-operator -o jsonpath='{.metadata.name}')
if [ "$roleName" != "demo-operator" ]; then
    echo "RBAC role demo-operator is deleted sucessfully, moving on..."
else
    echo "ERROR: the role 'demo-operator' was not deleted as expected! Exiting..."
    exit 1
fi

#check compliance change
 policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since it deleted the RBAC role demo-operator that is a 'mustnothave'"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since it deleted the RBAC role demo-operator that is a 'mustnothave'"
        
    else 
        sleep $frequency
        policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting"
        exit 1
    fi   
fi

echo -e "
------test 5 succeeded-----
"
}
#################################################
# test 6: changing the policy compliance to musthave with 'enforce' (should remain 'Compliant')
#################################################
test_6(){


echo -e "
------test 6-----
changing the policy compliance to musthave with 'enforce' (should remain 'Compliant')
-----------------
"

kubectl -n=$namespace apply -f- <<EOF
apiVersion: compliance.mcm.ibm.com/v1alpha1
kind: Compliance
metadata:
  name: demo-compliance
  description: Instance descriptor for compliance resource
spec:
  runtime-rules:
    - apiVersion: policy.mcm.ibm.com/v1alpha1
      kind: Policy
      metadata:
        name: demo-policy
        namespace: $namespace
      spec:
        remediationAction: "enforce"
        complianceType: "musthave" 
        namespaces:
          include: $includeNamespaces
          exclude: $excludeNamespaces
        role-templates:
          - apiVersion: roletemplate.mcm.ibm.com/v1alpha1
            metadata:
              namespace: "" # will be inferred
              name: demo-operator
            complianceType: "musthave" # at this level, it means the role must exist with the rules that it musthave below
            rules:
              - complianceType: "musthave" # at this level, it means if the role exists the rule is a musthave
                policyRule:
                  apiGroups: ["core"]
                  resources: ["pods"]
                  verbs: ["get", "list"]
        object-templates:
          - complianceType: "musthave" 
            objectDefinition:
              kind: Namespace
              apiVersion: v1
              metadata:
                name: demo-namespace
                labels:
                  name: production
EOF
sleep $frequency


# check role creation by the controller
sleep $frequency
roleName=$(kubectl -n=default get role demo-operator -o jsonpath='{.metadata.name}')
if [ "$roleName" = "demo-operator" ]; then
    echo "RBAC role demo-operator is created sucessfully, moving on..."
else
    echo "ERROR: the role 'demo-operator' was not created as expected! Exiting..."
    exit 1
fi

namespaceName=$(kubectl  -n=default get namespace demo-namespace -o jsonpath='{.metadata.name}')
if [ "$namespaceName" = "demo-namespace" ]; then
    echo "namespace 'demo-namespace' is created sucessfully, moving on..."
else
    echo "ERROR: the namespace 'demo-namespace' was not created as expected! Exiting..."
    exit 1
fi

#check compliance change
 policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e  "
=> policy status is correct, it is 'Compliant' since it created the RBAC role 'demo-operator' and the namespace 'demo-namespace'  that is a 'musthave'"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since it created the RBAC role 'demo-operator' and the namespace 'demo-namespace'  that is a 'musthave'"    
    else 
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting"
        exit 1
    fi   
fi

echo -e "
------test 6 succeeded-----
"
}

#################################################
# test 7: creating a mismatch in the RBAC role operator verbs, and the policy rules (should remain 'Compliant')
#################################################
test_7(){


echo -e "
------test 7-----
creating a mismatch in the RBAC role operator verbs, and the policy rules (should remain 'Compliant')
-----------------
"
kubectl apply -f- <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: demo-operator
  namespace: $namespace
rules:
- apiGroups:
  - core
  resources:
  - pods
  verbs:
  - list
EOF
sleep $frequency

roleVerbs=$(kubectl get role demo-operator -o jsonpath='{.rules[*].verbs}')

if [ "$roleVerbs" = "[get list]" ]; then
echo -e "
=> the role verbs have been successfully patched to: $roleVerbs"
else
    sleep $frequency
    roleVerbs=$(kubectl get role demo-operator -o jsonpath='{.rules[*].verbs}')
    if [ "$roleVerbs" = "[get list]" ]; then
echo -e "
=> the role verbs have been successfully patched to: $roleVerbs"   
    else 
        echo "ERROR: the role verbs have NOT been successfully patched, they remain: $roleVerbs. Exiting..."
        exit 1
    fi   
fi

#check compliance change
 policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since the RBAC role 'demo-operator' and the namespace 'demo-namespace' exist"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since it deleted the RBAC role 'demo-operator' and the namespace 'demo-namespace' exist"    
    else 
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting..."
        exit 1
    fi   
fi
echo -e "
------test 7 succeeded-----
"

}
#################################################
# test 8: changing the policy remediationAction to 'inform' compliance with musthave (should remain 'Compliant')
#################################################
test_8(){


echo -e "
------test 8-----
changing the policy remediationAction to 'inform' compliance with musthave (should remain 'Compliant')
-----------------
"

kubectl -n=$namespace apply -f- <<EOF
apiVersion: compliance.mcm.ibm.com/v1alpha1
kind: Compliance
metadata:
  name: demo-compliance
  description: Instance descriptor for compliance resource
spec:
  runtime-rules:
    - apiVersion: policy.mcm.ibm.com/v1alpha1
      kind: Policy
      metadata:
        name: demo-policy
        namespace: $namespace
      spec:
        remediationAction: "inform"
        complianceType: "musthave" 
        namespaces:
          include: $includeNamespaces
          exclude: $excludeNamespaces
        role-templates:
          - apiVersion: roletemplate.mcm.ibm.com/v1alpha1
            metadata:
              namespace: "" # will be inferred
              name: demo-operator
            complianceType: "musthave" # at this level, it means the role must exist with the rules that it musthave below
            rules:
              - complianceType: "musthave" # at this level, it means if the role exists the rule is a musthave
                policyRule:
                  apiGroups: ["core"]
                  resources: ["pods"]
                  verbs: ["get", "list"]
        object-templates:
          - complianceType: "musthave" 
            objectDefinition:
              kind: Namespace
              apiVersion: v1
              metadata:
                name: demo-namespace
                labels:
                  name: production
EOF
sleep $frequency

#check compliance change
 policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since the RBAC role 'demo-operator' and the namespace 'demo-namespace' exist"
else
    sleep $frequency
    policyStatus=$(kubectl -n=$namespace get pl demo-policy -o jsonpath='{.status.compliant}')
    if [ "$policyStatus" = "Compliant" ]; then
echo -e "
=> policy status is correct, it is 'Compliant' since it deleted the RBAC role 'demo-operator' and the namespace 'demo-namespace' exist"    
    else 
        echo "ERROR: policy status should be 'Compliant', but it is not. Exiting..."
        exit 1
    fi   
fi
echo -e "
------test 8 succeeded-----
"
}


#################################################
#ctrl_c:
#################################################
function ctrl_c() {
    cleanup
    echo -e "


 ________________________________________
|   stopping the test script, good bye!  |
 ----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                
                ||     ||


"
    exit 1
}

#################################################
#test flow:
#################################################

trap ctrl_c INT

initialize $1 $2 $3 $4 $5

for (( count=1; count<=$iterations; count++ ))
do  
    echo -e "
performing the iteration number $count of the tests
    "
    cleanup
    create_compliance
    test_1
    if [ "$interactive" = "true" ]; then
      read -p "test 1 passed! Press 'enter' to continue to the next test"
    fi
    test_2
    if [ "$interactive" = "true" ]; then
      read -p "test 2 passed! Press 'enter' to continue to the next test"
    fi
    test_3
    if [ "$interactive" = "true" ]; then
      read -p "test 3 passed! Press 'enter' to continue to the next test"
    fi
    test_4
    if [ "$interactive" = "true" ]; then
      read -p "test 4 passed! Press 'enter' to continue to the next test"
    fi
    test_5
    if [ "$interactive" = "true" ]; then
      read -p "test 5 passed! Press 'enter' to continue to the next test"
    fi
    test_6
    if [ "$interactive" = "true" ]; then
      read -p "test 6 passed! Press 'enter' to continue to the next test"
    fi
    test_7
    if [ "$interactive" = "true" ]; then
      read -p "test 7 passed! Press 'enter' to continue to the next test"
    fi
    test_8
    if [ "$interactive" = "true" ]; then
      read -p "All tests passed! Press 'enter' to cleanup and exit"
    fi
done

cleanup


#################################################
# summary:
#################################################
echo -e "
------summary-----
Congratulations!
all tests in $iterations iterations have completed successfully!!!
-----------------
"