├── .gitattributes ├── .gitignore ├── Dockerfile ├── Jenkinsfile ├── README.md ├── cis-etcd.sh ├── cis-kubelet.sh ├── cis-master.sh ├── integration-test-PROD.sh ├── integration-test.sh ├── k8s-PROD-deployment-rollout-status.sh ├── k8s-deployment-rollout-status.sh ├── k8s-deployment.sh ├── k8s_PROD-deployment_service.yaml ├── k8s_deployment_service.yaml ├── kube-scan.yaml ├── kubesec-scan.sh ├── opa-docker-security.rego ├── opa-k8s-security.rego ├── pom.xml ├── sec_files ├── file1 └── file3 ├── setup ├── azure-vm-template │ ├── parameters.json │ └── template.json ├── gcp-vm-gcloud-cmd │ └── gcloud-cmds.md ├── jenkins-plugins │ ├── installer.sh │ └── plugins.txt └── vm-install-script │ └── install-script.sh ├── slack-emojis ├── deadpool.png ├── github.png ├── hulk.jpg ├── jenkins.png ├── k8s.png └── ww.png ├── src ├── main │ ├── java │ │ └── com │ │ │ └── devsecops │ │ │ ├── NumericApplication.java │ │ │ ├── NumericController.java │ │ │ └── WebSecurityConfig.java │ └── resources │ │ └── application.properties └── test │ └── java │ └── com │ └── devsecops │ └── NumericApplicationTests.java ├── trivy-docker-image-scan.sh ├── trivy-k8s-scan.sh ├── vars └── sendNotification.groovy ├── zap.sh └── zap_rules /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .settings 25 | bin 26 | .classpath 27 | .project 28 | target -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk8:alpine-slim 2 | EXPOSE 8080 3 | ARG JAR_FILE=target/*.jar 4 | RUN addgroup -S pipeline && adduser -S k8s-pipeline -G pipeline 5 | COPY ${JAR_FILE} /home/k8s-pipeline/app.jar 6 | USER k8s-pipeline 7 | ENTRYPOINT ["java","-jar","/home/k8s-pipeline/app.jar"] -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('slack') _ 2 | 3 | 4 | /////// ******************************* Code for fectching Failed Stage Name ******************************* /////// 5 | import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor 6 | import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper 7 | import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper 8 | import org.jenkinsci.plugins.workflow.actions.ErrorAction 9 | 10 | // Get information about all stages, including the failure cases 11 | // Returns a list of maps: [[id, failedStageName, result, errors]] 12 | @NonCPS 13 | List getStageResults( RunWrapper build ) { 14 | 15 | // Get all pipeline nodes that represent stages 16 | def visitor = new PipelineNodeGraphVisitor( build.rawBuild ) 17 | def stages = visitor.pipelineNodes.findAll{ it.type == FlowNodeWrapper.NodeType.STAGE } 18 | 19 | return stages.collect{ stage -> 20 | 21 | // Get all the errors from the stage 22 | def errorActions = stage.getPipelineActions( ErrorAction ) 23 | def errors = errorActions?.collect{ it.error }.unique() 24 | 25 | return [ 26 | id: stage.id, 27 | failedStageName: stage.displayName, 28 | result: "${stage.status.result}", 29 | errors: errors 30 | ] 31 | } 32 | } 33 | 34 | // Get information of all failed stages 35 | @NonCPS 36 | List getFailedStages( RunWrapper build ) { 37 | return getStageResults( build ).findAll{ it.result == 'FAILURE' } 38 | } 39 | 40 | /////// ******************************* Code for fectching Failed Stage Name ******************************* /////// 41 | 42 | pipeline { 43 | agent any 44 | 45 | environment { 46 | deploymentName = "devsecops" 47 | containerName = "devsecops-container" 48 | serviceName = "devsecops-svc" 49 | imageName = "siddharth67/numeric-app:${GIT_COMMIT}" 50 | applicationURL="http://devsecops-demo.eastus.cloudapp.azure.com" 51 | applicationURI="/increment/99" 52 | } 53 | 54 | stages { 55 | 56 | // stage('Build Artifact - Maven') { 57 | // steps { 58 | // sh "mvn clean package -DskipTests=true" 59 | // archive 'target/*.jar' 60 | // } 61 | // } 62 | 63 | // stage('Unit Tests - JUnit and JaCoCo') { 64 | // steps { 65 | // sh "mvn test" 66 | // } 67 | // } 68 | 69 | // stage('Mutation Tests - PIT') { 70 | // steps { 71 | // sh "mvn org.pitest:pitest-maven:mutationCoverage" 72 | // } 73 | // } 74 | 75 | // stage('SonarQube - SAST') { 76 | // steps { 77 | // withSonarQubeEnv('SonarQube') { 78 | // sh "mvn sonar:sonar \ 79 | // -Dsonar.projectKey=numeric-application \ 80 | // -Dsonar.host.url=http://devsecops-demo.eastus.cloudapp.azure.com:9000" 81 | // } 82 | // timeout(time: 2, unit: 'MINUTES') { 83 | // script { 84 | // waitForQualityGate abortPipeline: true 85 | // } 86 | // } 87 | // } 88 | // } 89 | 90 | // stage('Vulnerability Scan - Docker') { 91 | // steps { 92 | // parallel( 93 | // "Dependency Scan": { 94 | // sh "mvn dependency-check:check" 95 | // }, 96 | // "Trivy Scan":{ 97 | // sh "bash trivy-docker-image-scan.sh" 98 | // }, 99 | // "OPA Conftest":{ 100 | // sh 'docker run --rm -v $(pwd):/project openpolicyagent/conftest test --policy opa-docker-security.rego Dockerfile' 101 | // } 102 | // ) 103 | // } 104 | // } 105 | 106 | 107 | // stage('Docker Build and Push') { 108 | // steps { 109 | // withDockerRegistry([credentialsId: "docker-hub", url: ""]) { 110 | // sh 'printenv' 111 | // sh 'sudo docker build -t siddharth67/numeric-app:""$GIT_COMMIT"" .' 112 | // sh 'docker push siddharth67/numeric-app:""$GIT_COMMIT""' 113 | // } 114 | // } 115 | // } 116 | 117 | // stage('Vulnerability Scan - Kubernetes') { 118 | // steps { 119 | // parallel( 120 | // "OPA Scan": { 121 | // sh 'docker run --rm -v $(pwd):/project openpolicyagent/conftest test --policy opa-k8s-security.rego k8s_deployment_service.yaml' 122 | // }, 123 | // "Kubesec Scan": { 124 | // sh "bash kubesec-scan.sh" 125 | // }, 126 | // "Trivy Scan": { 127 | // sh "bash trivy-k8s-scan.sh" 128 | // } 129 | // ) 130 | // } 131 | // } 132 | 133 | // stage('K8S Deployment - DEV') { 134 | // steps { 135 | // parallel( 136 | // "Deployment": { 137 | // withKubeConfig([credentialsId: 'kubeconfig']) { 138 | // sh "bash k8s-deployment.sh" 139 | // } 140 | // }, 141 | // "Rollout Status": { 142 | // withKubeConfig([credentialsId: 'kubeconfig']) { 143 | // sh "bash k8s-deployment-rollout-status.sh" 144 | // } 145 | // } 146 | // ) 147 | // } 148 | // } 149 | 150 | // stage('Integration Tests - DEV') { 151 | // steps { 152 | // script { 153 | // try { 154 | // withKubeConfig([credentialsId: 'kubeconfig']) { 155 | // sh "bash integration-test.sh" 156 | // } 157 | // } catch (e) { 158 | // withKubeConfig([credentialsId: 'kubeconfig']) { 159 | // sh "kubectl -n default rollout undo deploy ${deploymentName}" 160 | // } 161 | // throw e 162 | // } 163 | // } 164 | // } 165 | // } 166 | 167 | // stage('OWASP ZAP - DAST') { 168 | // steps { 169 | // withKubeConfig([credentialsId: 'kubeconfig']) { 170 | // sh 'bash zap.sh' 171 | // } 172 | // } 173 | // } 174 | 175 | // stage('Prompte to PROD?') { 176 | // steps { 177 | // timeout(time: 2, unit: 'DAYS') { 178 | // input 'Do you want to Approve the Deployment to Production Environment/Namespace?' 179 | // } 180 | // } 181 | // } 182 | 183 | // stage('K8S CIS Benchmark') { 184 | // steps { 185 | // script { 186 | 187 | // parallel( 188 | // "Master": { 189 | // sh "bash cis-master.sh" 190 | // }, 191 | // "Etcd": { 192 | // sh "bash cis-etcd.sh" 193 | // }, 194 | // "Kubelet": { 195 | // sh "bash cis-kubelet.sh" 196 | // } 197 | // ) 198 | 199 | // } 200 | // } 201 | // } 202 | 203 | // stage('K8S Deployment - PROD') { 204 | // steps { 205 | // parallel( 206 | // "Deployment": { 207 | // withKubeConfig([credentialsId: 'kubeconfig']) { 208 | // sh "sed -i 's#replace#${imageName}#g' k8s_PROD-deployment_service.yaml" 209 | // sh "kubectl -n prod apply -f k8s_PROD-deployment_service.yaml" 210 | // } 211 | // }, 212 | // "Rollout Status": { 213 | // withKubeConfig([credentialsId: 'kubeconfig']) { 214 | // sh "bash k8s-PROD-deployment-rollout-status.sh" 215 | // } 216 | // } 217 | // ) 218 | // } 219 | // } 220 | 221 | // stage('Integration Tests - PROD') { 222 | // steps { 223 | // script { 224 | // try { 225 | // withKubeConfig([credentialsId: 'kubeconfig']) { 226 | // sh "bash integration-test-PROD.sh" 227 | // } 228 | // } catch (e) { 229 | // withKubeConfig([credentialsId: 'kubeconfig']) { 230 | // sh "kubectl -n prod rollout undo deploy ${deploymentName}" 231 | // } 232 | // throw e 233 | // } 234 | // } 235 | // } 236 | // } 237 | 238 | stage('Testing Slack - 1') { 239 | steps { 240 | sh 'exit 0' 241 | } 242 | } 243 | 244 | stage('Testing Slack - Error Stage') { 245 | steps { 246 | sh 'exit 0' 247 | } 248 | } 249 | 250 | } 251 | 252 | post { 253 | // always { 254 | // junit 'target/surefire-reports/*.xml' 255 | // jacoco execPattern: 'target/jacoco.exec' 256 | // pitmutation mutationStatsFile: '**/target/pit-reports/**/mutations.xml' 257 | // dependencyCheckPublisher pattern: 'target/dependency-check-report.xml' 258 | // publishHTML([allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'owasp-zap-report', reportFiles: 'zap_report.html', reportName: 'OWASP ZAP HTML Report', reportTitles: 'OWASP ZAP HTML Report']) 259 | 260 | // //Use sendNotifications.groovy from shared library and provide current build result as parameter 261 | // //sendNotification currentBuild.result 262 | // } 263 | 264 | success { 265 | script { 266 | /* Use slackNotifier.groovy from shared library and provide current build result as parameter */ 267 | env.failedStage = "none" 268 | env.emoji = ":white_check_mark: :tada: :thumbsup_all:" 269 | sendNotification currentBuild.result 270 | } 271 | } 272 | 273 | failure { 274 | script { 275 | //Fetch information about failed stage 276 | def failedStages = getFailedStages( currentBuild ) 277 | env.failedStage = failedStages.failedStageName 278 | env.emoji = ":x: :red_circle: :sos:" 279 | sendNotification currentBuild.result 280 | } 281 | } 282 | } 283 | 284 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DevSecOps - Kubernetes DevOps & Security 3 | 4 | These are the code files from the [DevSecOps course](https://kodekloud.com/courses/devsecops/) hosted on KodeKloud. 5 | 6 | 7 | ### Fork and Clone this Repo 8 | 9 | ### Clone to Desktop and VM 10 | 11 | ### NodeJS Microservice - Docker Image - 12 | 13 | `docker run -p 8787:5000 siddharth67/node-service:v1` 14 | 15 | `curl localhost:8787/plusone/99` 16 | 17 | ### NodeJS Microservice - Kubernetes Deployment - 18 | `kubectl create deploy node-app --image siddharth67/node-service:v1` 19 | 20 | `kubectl expose deploy node-app --name node-service --port 5000 --type ClusterIP` 21 | 22 | `curl node-service-ip:5000/plusone/99` 23 | -------------------------------------------------------------------------------- /cis-etcd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #cis-etcd.sh 3 | 4 | total_fail=$(kube-bench run --targets etcd --version 1.15 --check 2.2 --json | jq .[].total_fail) 5 | 6 | if [[ "$total_fail" -ne 0 ]]; 7 | then 8 | echo "CIS Benchmark Failed ETCD while testing for 2.2" 9 | exit 1; 10 | else 11 | echo "CIS Benchmark Passed for ETCD - 2.2" 12 | fi; 13 | -------------------------------------------------------------------------------- /cis-kubelet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #cis-kubelet.sh 3 | 4 | total_fail=$(kube-bench run --targets node --version 1.15 --check 4.2.1,4.2.2 --json | jq .[].total_fail) 5 | 6 | if [[ "$total_fail" -ne 0 ]]; 7 | then 8 | echo "CIS Benchmark Failed Kubelet while testing for 4.2.1, 4.2.2" 9 | exit 1; 10 | else 11 | echo "CIS Benchmark Passed Kubelet for 4.2.1, 4.2.2" 12 | fi; 13 | -------------------------------------------------------------------------------- /cis-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #cis-master.sh 3 | 4 | total_fail=$(kube-bench master --version 1.15 --check 1.2.7,1.2.8,1.2.9 --json | jq .[].total_fail) 5 | 6 | if [[ "$total_fail" -ne 0 ]]; 7 | then 8 | echo "CIS Benchmark Failed MASTER while testing for 1.2.7, 1.2.8, 1.2.9" 9 | exit 1; 10 | else 11 | echo "CIS Benchmark Passed for MASTER - 1.2.7, 1.2.8, 1.2.9" 12 | fi; -------------------------------------------------------------------------------- /integration-test-PROD.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sleep 5s 3 | 4 | # echo "ok" 5 | # PORT=$(kubectl get svc ${serviceName} -o json | jq .spec.ports[].nodePort) 6 | 7 | ### Istio Ingress Gateway Port 80 - NodePort 8 | PORT=$(kubectl -n istio-system get svc istio-ingressgateway -o json | jq '.spec.ports[] | select(.port == 80)' | jq .nodePort) 9 | 10 | 11 | echo $PORT 12 | echo $applicationURL:$PORT$applicationURI 13 | 14 | if [[ ! -z "$PORT" ]]; 15 | then 16 | 17 | response=$(curl -s $applicationURL:$PORT$applicationURI) 18 | http_code=$(curl -s -o /dev/null -w "%{http_code}" $applicationURL:$PORT$applicationURI) 19 | 20 | if [[ "$response" == 100 ]]; 21 | then 22 | echo "Increment Test Passed" 23 | else 24 | echo "Increment Test Failed" 25 | exit 1; 26 | fi; 27 | 28 | if [[ "$http_code" == 200 ]]; 29 | then 30 | echo "HTTP Status Code Test Passed" 31 | else 32 | echo "HTTP Status code is not 200" 33 | exit 1; 34 | fi; 35 | 36 | else 37 | echo "The Service does not have a NodePort" 38 | exit 1; 39 | fi; -------------------------------------------------------------------------------- /integration-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #integration-test.sh 4 | 5 | sleep 5s 6 | 7 | PORT=$(kubectl -n default get svc ${serviceName} -o json | jq .spec.ports[].nodePort) 8 | 9 | echo $PORT 10 | echo $applicationURL:$PORT/$applicationURI 11 | 12 | if [[ ! -z "$PORT" ]]; 13 | then 14 | 15 | response=$(curl -s $applicationURL:$PORT$applicationURI) 16 | http_code=$(curl -s -o /dev/null -w "%{http_code}" $applicationURL:$PORT$applicationURI) 17 | 18 | if [[ "$response" == 100 ]]; 19 | then 20 | echo "Increment Test Passed" 21 | else 22 | echo "Increment Test Failed" 23 | exit 1; 24 | fi; 25 | 26 | if [[ "$http_code" == 200 ]]; 27 | then 28 | echo "HTTP Status Code Test Passed" 29 | else 30 | echo "HTTP Status code is not 200" 31 | exit 1; 32 | fi; 33 | 34 | else 35 | echo "The Service does not have a NodePort" 36 | exit 1; 37 | fi; 38 | -------------------------------------------------------------------------------- /k8s-PROD-deployment-rollout-status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sleep 60s 3 | 4 | if [[ $(kubectl -n prod rollout status deploy ${deploymentName} --timeout 5s) != *"successfully rolled out"* ]]; 5 | then 6 | echo "Deployment ${deploymentName} Rollout has Failed" 7 | kubectl -n prod rollout undo deploy ${deploymentName} 8 | exit 1; 9 | else 10 | echo "Deployment ${deploymentName} Rollout is Success" 11 | fi -------------------------------------------------------------------------------- /k8s-deployment-rollout-status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #k8s-deployment-rollout-status.sh 4 | 5 | sleep 60s 6 | 7 | if [[ $(kubectl -n default rollout status deploy ${deploymentName} --timeout 5s) != *"successfully rolled out"* ]]; 8 | then 9 | echo "Deployment ${deploymentName} Rollout has Failed" 10 | kubectl -n default rollout undo deploy ${deploymentName} 11 | exit 1; 12 | else 13 | echo "Deployment ${deploymentName} Rollout is Success" 14 | fi -------------------------------------------------------------------------------- /k8s-deployment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #k8s-deployment.sh 4 | 5 | sed -i "s#replace#${imageName}#g" k8s_deployment_service.yaml 6 | # kubectl -n default get deployment ${deploymentName} > /dev/null 7 | 8 | # if [[ $? -ne 0 ]]; then 9 | # echo "deployment ${deploymentName} doesnt exist" 10 | # kubectl -n default apply -f k8s_deployment_service.yaml 11 | # else 12 | # echo "deployment ${deploymentName} exist" 13 | # echo "image name - ${imageName}" 14 | # kubectl -n default set image deploy ${deploymentName} ${containerName}=${imageName} --record=true 15 | # fi 16 | 17 | 18 | kubectl -n default apply -f k8s_deployment_service.yaml -------------------------------------------------------------------------------- /k8s_PROD-deployment_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: devsecops 6 | name: devsecops 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: devsecops 12 | strategy: {} 13 | template: 14 | metadata: 15 | labels: 16 | app: devsecops 17 | spec: 18 | serviceAccountName: default 19 | volumes: 20 | - name: vol 21 | emptyDir: {} 22 | containers: 23 | - image: replace 24 | name: devsecops-container 25 | volumeMounts: 26 | - mountPath: /tmp 27 | name: vol 28 | securityContext: 29 | capabilities: 30 | drop: 31 | - NET_RAW 32 | runAsUser: 100 33 | runAsNonRoot: true 34 | readOnlyRootFilesystem: true 35 | allowPrivilegeEscalation: false 36 | resources: 37 | requests: 38 | memory: "256Mi" 39 | cpu: "200m" 40 | limits: 41 | memory: "512Mi" 42 | cpu: "500m" 43 | --- 44 | apiVersion: v1 45 | kind: Service 46 | metadata: 47 | labels: 48 | app: devsecops 49 | name: devsecops-svc 50 | spec: 51 | ports: 52 | - port: 8080 53 | protocol: TCP 54 | targetPort: 8080 55 | selector: 56 | app: devsecops 57 | type: ClusterIP -------------------------------------------------------------------------------- /k8s_deployment_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: devsecops 6 | name: devsecops 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: devsecops 12 | strategy: {} 13 | template: 14 | metadata: 15 | labels: 16 | app: devsecops 17 | spec: 18 | volumes: 19 | - name: vol 20 | emptyDir: {} 21 | serviceAccountName: default 22 | containers: 23 | - image: replace 24 | name: devsecops-container 25 | volumeMounts: 26 | - mountPath: /tmp 27 | name: vol 28 | securityContext: 29 | runAsNonRoot: true 30 | runAsUser: 100 31 | readOnlyRootFilesystem: true 32 | --- 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | labels: 37 | app: devsecops 38 | name: devsecops-svc 39 | spec: 40 | ports: 41 | - port: 8080 42 | protocol: TCP 43 | targetPort: 8080 44 | selector: 45 | app: devsecops 46 | type: NodePort -------------------------------------------------------------------------------- /kube-scan.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kube-scan 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: kube-scan 10 | namespace: kube-scan 11 | labels: 12 | app: kube-scan 13 | data: 14 | risk-config.yaml: | 15 | expConst: 9 16 | impactConst: 4 17 | attackVector: 18 | remote: 0.85 19 | local: 0.55 20 | exploitability: 21 | high: 0.54 22 | moderate: 0.4 23 | low: 0.1 24 | veryLow: 0.05 25 | scopeFactor: 26 | none: 0.25 27 | host: 1 28 | cluster: 1 29 | ciaScore: 30 | high: 0.56 31 | low: 0.22 32 | none: 0 33 | riskCategory: 34 | min: 0 35 | low: 3 36 | medium: 6 37 | max: 10 38 | individualRiskCategory: 39 | min: 0 40 | low: 3 41 | medium: 5 42 | max: 10 43 | basic: 44 | - name: "privileged" 45 | title: "Workload is privileged" 46 | shortDescription: "Processes inside a privileged containers get full access to the host" 47 | description: "Processes inside a privileged container will have full access to the host, which means any third-party library or malicious program can compromise the host. As a result, the compromised host could be used to compromise the entire cluster" 48 | confidentiality: "High" 49 | confidentialityDescription: "Privileged containers may have the option to read and modify any application, such as Docker, Kubernetes, etc" 50 | integrity: "Low" 51 | integrityDescription: "Processes inside a privileged container get full access to the host. This means a malicious program or third-party library can compromise the host and the entire cluster" 52 | availability: "Low" 53 | availabilityDescription: "Processes inside a privileged container may have the ability to modify or stop Kubernetes, Docker and other applications" 54 | exploitability: "Moderate" 55 | attackVector: "Local" 56 | scope: "Host" 57 | handler: "IsPrivileged" 58 | - name: "runningAsRoot" 59 | title: "Workload may have containers running as root" 60 | shortDescription: "Processes in container running as root may be able to escape their container" 61 | description: "Workload does not specify a non-root user for its containers to run as and does not specify runAsNonRoot. Processes inside a container running as root may be able to escape that container and perform malicious actions on the host - basically giving them complete control over the host and the ability to compromise the entire cluster" 62 | confidentiality: "High" 63 | confidentialityDescription: "Root processes that can escape the container have the ability to read secrets from Kubernetes, Docker and other applications" 64 | integrity: "Low" 65 | integrityDescription: "Processes in a container running as root may be able to escape their container and perform malicious actions on the host" 66 | availability: "Low" 67 | availabilityDescription: "Root processes that can escape the container have the ability to modify or stop Kubernetes, Docker and other applications" 68 | exploitability: "Low" 69 | attackVector: "Local" 70 | scope: "Host" 71 | handler: "IsRunningAsRoot" 72 | - name: "AllowPrivilegeEscalation" 73 | shortDescription: "Privilege escalation allows programs inside the container to run as root" 74 | description: "Privilege escalation allows programs inside the container to run as root, even if the main process is not root, which can give those programs control over that container, host and even cluster" 75 | title: "Workload allows privilege escalation" 76 | confidentiality: "Low" 77 | confidentialityDescription: "Root processes that can escape the containers have the ability to read secrets from Kubernetes, Docker and other applications" 78 | integrity: "Low" 79 | integrityDescription: "Processes in a container running as root may be able to escape their container and perform malicious actions on the host" 80 | availability: "Low" 81 | availabilityDescription: "Root processes that can escape the containers have the ability to modify or stop Kubernetes, Docker and other applications" 82 | exploitability: "VeryLow" 83 | attackVector: "Local" 84 | scope: "Host" 85 | handler: "IsPrivilegedEscalation" 86 | # - name: "CapNetRaw" 87 | # title: "Workload has a container(s) with NET_RAW capability" 88 | # shortDescription: "NET_RAW capability enables ARP spoofing from the container\nNET_RAW capability enables the container to craft malicious raw packet" 89 | # description: "The capability NET_RAW allows the container to craft any packet, including malformed or malicious packets" 90 | # confidentiality: "High" 91 | # confidentialityDescription: "This capability enables ARP spoofing from the container, which means UDP packets can be sent with a forged source IP, etc. This enables the container to perform Man-in-the-Middle (MitM) attacks on the host network" 92 | # integrity: "None" 93 | # integrityDescription: "" 94 | # availability: "Low" 95 | # availabilityDescription: "This capability enables the container to craft malicious raw packet, such as Ping of Death" 96 | # exploitability: "Low" 97 | # attackVector: "Local" 98 | # scope: "Cluster" 99 | # handler: "IsCapNetRaw" 100 | - name: "WritableFileSystem" 101 | title: "Workload has a container(s) with writable file system" 102 | shortDescription: "Writable File System allows the persistence of threats" 103 | description: "A writable file system allows files within the container to be changed. This means a malicious process inside the container can use a writable file system to store or manipulate data inside the container" 104 | confidentiality: "None" 105 | confidentialityDescription: "" 106 | integrity: "Low" 107 | integrityDescription: "This allows malicious processes to write data to disk, making it easier to drop and execute external malicious code" 108 | availability: "None" 109 | availabilityDescription: "" 110 | exploitability: "Moderate" 111 | attackVector: "Local" 112 | scope: "None" 113 | handler: "IsWritableFileSystem" 114 | - name: "UnmaskedProcMount" 115 | title: "Workload exposes unsafe parts of /proc" 116 | shortDescription: "Full access to /proc can reveal information about the host and other containers\n/proc/sys allows a privileged user to change the kernel parameters are runtime" 117 | description: "A container with full access (unmasked) to the host’s /proc command is able to retrieve information about all the activities and users on that host. /proc/sys allows a privileged user to change the runtime kernel parameters and impact how resources are shared amongst containers" 118 | confidentiality: "Low" 119 | confidentialityDescription: "/proc contains information about all network connections on the host, the file systems and permissions, running processes, etc" 120 | integrity: "None" 121 | integrityDescription: "" 122 | availability: "High" 123 | availabilityDescription: "/proc/sys allows a privileged user to change the runtime kernel parameters, which may impact how resources are shared amongst containers" 124 | exploitability: "Moderate" 125 | attackVector: "Local" 126 | scope: "Host" 127 | handler: "IsUnmaskedProcMount" 128 | - name: "AllowedUnsafeSysctls" 129 | title: "Workload allows unsafe allocation of CPU resources" 130 | shortDescription: "Sysctl allows users to modify the kernel settings at run time: networking, memory, etc. Some sysctl interfaces can affect other containers, the host or bypass the CPU quota attributed to the container" 131 | description: "Sysctl is an interface that enables the container’s parameters to be changed, which could allow the container to grab more CPU resources than it’s allowed by its quota. This may starve other containers from CPU cycles, compromising the operations of the container, host and even the entire cluster" 132 | confidentiality: "None" 133 | confidentialityDescription: "" 134 | integrity: "Low" 135 | integrityDescription: "Some of the sysctl interfaces allow the container to affect the performance of other containers and/or the host" 136 | availability: "High" 137 | availabilityDescription: "Some of the sysctl interfaces allow the container to grab more CPU resources than allowed by their quota. This may starve other containers from CPU cycles" 138 | exploitability: "Moderate" 139 | attackVector: "Local" 140 | scope: "Host" 141 | handler: "IsAllowedUnsafeSysctls" 142 | - name: "notConfiguredCpuOrMemoryLimit" 143 | title: "Workload has a container which its CPU or Memory limit was not configured" 144 | shortDescription: "CPU and Memory quotas prevent container from grabbing too many resources from the node, and allow a better scheduling of resources across the cluster" 145 | description: "CPU and Memory quotas prevent container from grabbing too many resources from the node, and allow a better scheduling of resources across the cluster" 146 | confidentiality: "None" 147 | confidentialityDescription: "" 148 | integrity: "None" 149 | integrityDescription: "" 150 | availability: "High" 151 | availabilityDescription: "Workloads with no CPU or memory quota may starve off other workloads on the node, resulting in pod ejections and cascading reschedule of pods on other nodes" 152 | exploitability: "Moderate" 153 | attackVector: "Local" 154 | scope: "Host" 155 | handler: "IsNotConfiguredCpuOrMemoryLimit" 156 | - name: "mountingOSDirectoryRW" 157 | title: "Workload is mounting a volume with OS Directory write permissions" 158 | shortDescription: "Containers can mount sensitive folders from the hosts, giving them potential dangerous access critical host configurations and binaries" 159 | description: "Containers can mount sensitive folders from the hosts, giving them potentially dangerous access to critical host configurations and binaries" 160 | confidentiality: "High" 161 | confidentialityDescription: "Sharing sensitive folders and files such as / (root), /var/run/, etc., can allow the container to communicate with other host applications, such as a database, which could expose sensitive information" 162 | integrity: "High" 163 | integrityDescription: "Sharing sensitive folders and files, such as / (root), /var/run/, docker.sock, etc. can allow the container to reconfigure the Kubernetes clusters, run new container images, etc" 164 | availability: "Low" 165 | availabilityDescription: "Sharing sensitive folders and files, such as / (root), /var/run/, docker.sock, etc. can allow the container to reconfigure the container quotas, run new container images, etc" 166 | exploitability: "Moderate" 167 | attackVector: "Local" 168 | scope: "Host" 169 | handler: "IsMountingOSDirectoryRW" 170 | - name: "mountingOSDirectoryRO" 171 | title: "Workload is mounting a volume with OS Directory read-only permissions" 172 | shortDescription: "Containers can mount sensitive folders from the hosts, giving them potential dangerous access critical host configurations" 173 | description: "Containers can mount sensitive folders from the hosts, giving them potential dangerous knowledge of critical host configurations" 174 | confidentiality: "Low" 175 | confidentialityDescription: "Sharing sensitive folders and files, such as /etc, /var/run/, etc., can allow the container to read secrets" 176 | integrity: "None" 177 | integrityDescription: "" 178 | availability: "None" 179 | availabilityDescription: "" 180 | exploitability: "Low" 181 | attackVector: "Local" 182 | scope: "Host" 183 | handler: "IsMountingOSDirectoryRO" 184 | - name: "capSysAdmin" 185 | title: "Workload has container/s with CAP_SYS_ADMIN capability" 186 | shortDescription: "CAP_SYS_ADMIN is the most privileged capability with over 150 privileged system calls allowed" 187 | description: "CAP_SYS_ADMIN is the most privileged capability allowed, out of more than 150 privileged system calls available" 188 | confidentiality: "High" 189 | confidentialityDescription: "CAP_SYS_ADMIN gives processes privileges equivalent to running as root. Processes in a container running as root may be able to escape their container and perform malicious actions on the host" 190 | integrity: "None" 191 | integrityDescription: "" 192 | availability: "None" 193 | availabilityDescription: "" 194 | exploitability: "Moderate" 195 | attackVector: "Local" 196 | scope: "Host" 197 | handler: "IsCapSysAdmin" 198 | - name: "ExposedByLoadBalancer" 199 | title: "Workload is exposed through a load balancer" 200 | shortDescription: "The service is accessible from other networks and/or from the Internet" 201 | description: "A load balancer is exposing the workload, making it accessible from other networks and the Internet" 202 | confidentiality: "High" 203 | confidentialityDescription: "Accidental exposure of sensitive services may lead to the exfiltration of confidential data through remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 204 | integrity: "Low" 205 | integrityDescription: "Services open to the Internet may be used to access unprotected services (move laterally) by leveraging remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 206 | availability: "High" 207 | availabilityDescription: "Accidental exposure to the Internet can make the workload susceptible to DoS attacks from random attackers" 208 | exploitability: "Moderate" 209 | attackVector: "Remote" 210 | scope: "None" 211 | handler: "IsExposedByLoadBalancer" 212 | - name: "ExposedByNodePort" 213 | title: "Workload is exposed through a node port" 214 | shortDescription: "The service is accessible from other networks and/or from the Internet" 215 | description: "A node port is exposing the workload, making it accessible from other networks and the Internet" 216 | confidentiality: "High" 217 | confidentialityDescription: "Accidental exposure of sensitive services may lead to the exfiltration of confidential data through remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 218 | integrity: "Low" 219 | integrityDescription: "Services open to the Internet may be used to access unprotected services (move laterally) by leveraging remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 220 | availability: "High" 221 | availabilityDescription: "Accidental exposure to the Internet can make the workload susceptible to DoS attacks from random attackers" 222 | exploitability: "Moderate" 223 | attackVector: "Remote" 224 | scope: "None" 225 | handler: "IsExposedByNodePort" 226 | - name: "ExposedByIngress" 227 | title: "Workload is exposed through an ingress policy" 228 | shortDescription: "The service is accessible from other networks and/or from the Internet" 229 | description: "An ingress policy is exposing the workload, making it accessible from other networks and the Internet" 230 | confidentiality: "High" 231 | confidentialityDescription: "Accidental exposure of sensitive services may lead to the exfiltration of confidential data through remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 232 | integrity: "Low" 233 | integrityDescription: "Services open to the Internet may be used to access unprotected services (move laterally) by leveraging remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 234 | availability: "Low" 235 | availabilityDescription: "Accidental exposure to the Internet can make the workload susceptible to DoS attacks from random attackers" 236 | exploitability: "Moderate" 237 | attackVector: "Remote" 238 | scope: "None" 239 | handler: "IsExposedByIngress" 240 | - name: "HostPort" 241 | title: "Workload is exposed through a shared host port" 242 | shortDescription: "The service is accessible from other networks and/or from the Internet" 243 | description: "This container setting binds the container listening port to the IP address of the host. This exposes the pod to adjacent networks and/or to the Internet.\nA host port is exposing the workload, making it accessible from other networks and the Internet" 244 | confidentiality: "High" 245 | confidentialityDescription: "This setting binds the workload listening IP address to the host IP, making the service accessible from other networks and/or from the Internet" 246 | integrity: "Low" 247 | integrityDescription: "Services open to the Internet may be used to access unprotected services (move laterally) by leveraging remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 248 | availability: "High" 249 | availabilityDescription: "Accidental exposure to the Internet can make the workload susceptible to DoS attacks from random attackers" 250 | exploitability: "Moderate" 251 | attackVector: "Remote" 252 | scope: "None" 253 | handler: "IsHostPort" 254 | - name: "ShareHostNetwork" 255 | title: "Workload is exposed through a shared host network" 256 | shortDescription: "The service is accessible from other networks and/or from the Internet\nShare Host Network allows containers to sniff traffic from host and other containers" 257 | description: "This Security Context setting allows the workload to share the same network namespace as the host" 258 | confidentiality: "High" 259 | confidentialityDescription: "This allows the network to listen to the loopback interface and sniff the traffic to and from other pods. This setting also allows workloads to bind their listening IP address to the host IP, making the service accessible from other networks and/or from the Internet" 260 | integrity: "Low" 261 | integrityDescription: "Services open to the Internet may be used to access unprotected services (move laterally) by leveraging remote code vulnerabilities, vulnerable third-party libraries or vulnerable OS services" 262 | availability: "High" 263 | availabilityDescription: "Accidental exposure to the Internet can make the workload susceptible to DoS attacks from random attackers" 264 | exploitability: "Low" 265 | attackVector: "Remote" 266 | scope: "Host" 267 | handler: "IsShareHostNetwork" 268 | - name: "ShareHostPID" 269 | title: "Workload shares the host PID" 270 | shortDescription: "Share Host Pid allow containers to manipulate other container processes" 271 | description: "Shared host PIDs enable the sharing of processes with the host and other containers" 272 | confidentiality: "Low" 273 | confidentialityDescription: "Each container has access to password, secrets, certificates, etc. read by other containers" 274 | integrity: "Low" 275 | integrityDescription: "Each container can manipulate other container processes, inject malicious code, modify /proc, etc. A malicious container can move laterally by infecting other containers on the same host" 276 | availability: "Low" 277 | availabilityDescription: "Each container can crash another container’s processes" 278 | exploitability: "Low" 279 | attackVector: "Local" 280 | scope: "Host" 281 | handler: "IsShareHostPID" 282 | - name: "ShareHostIPC" 283 | title: "Workload shares the host IPC" 284 | shortDescription: "Shared Host IPC can leak confidential data sent from trusted applications" 285 | description: "IPC allows containers to communicate directly through shared memory - a shared IPC means that anyone in that namespace can access that memory" 286 | confidentiality: "High" 287 | confidentialityDescription: "Communication between trusted applications and untrusted applications (malicious third-party libraries, rogue containers) can leak confidential data" 288 | integrity: "Low" 289 | integrityDescription: "Untrusted applications can change the behavior of trusted applications through shared memory namespaces by tampering with the memory" 290 | availability: "Low" 291 | availabilityDescription: "An untrusted application can use improper Inter-Process Communications to crash the destination process" 292 | exploitability: "Low" 293 | attackVector: "Local" 294 | scope: "Host" 295 | handler: "IsShareHostIPC" 296 | remediation: 297 | - name: "seccomp" 298 | title: "Workload containers have a seccomp policy" 299 | shortDescription: "A seccomp policy specify which system class can be called by the application. It is a sandboxing technique that reduces the chance that a kernel vulnerability will be successfully exploited" 300 | description: "seccomp stands for Secure Computing mode - a seccomp policy can specify which system class can be called by the application. It is a sandboxing technique that reduces the chance that a kernel vulnerability will be successfully exploited" 301 | confidentiality: "High" 302 | confidentialityDescription: "A seccomp policy can prevent malicious programs from reading files not used by the container" 303 | integrity: "High" 304 | integrityDescription: "A seccomp policy can prevent malicious programs to use kernel exploits to break out of the container" 305 | availability: "High" 306 | availabilityDescription: "A seccomp policy can be used to restrict the system calls and prevent processes from grabbing additional CPU or memory resources" 307 | exploitability: "None" 308 | attackVector: "Local" 309 | scope: "Host" 310 | handler: "IsSecComp" 311 | - name: "selinux" 312 | title: "Workload containers have SELlinux or AppArmor enabled" 313 | shortDescription: "SELinux (RedHat-based distributions) and AppArmor(Debian-based distributions) provides access control policies. They can be used to restrict how processes can communicate" 314 | description: "SELinux (RedHat-based distributions) and AppArmor (Debian-based distributions) provides access control policies that can be used to restrict how processes can communicate to improve the overall security posture of the container and host" 315 | confidentiality: "High" 316 | confidentialityDescription: "The SELinux or AppArmor policy can be used to restrict what processes can read in each folder" 317 | integrity: "High" 318 | integrityDescription: "The SELinux or AppArmor policy can be used to restrict what processes can write to disk and in what folders" 319 | availability: "High" 320 | availabilityDescription: "The SELinux or AppArmor policy can be used to restrict the system calls and prevent processes from grabbing additional CPU or memory resources" 321 | exploitability: "None" 322 | attackVector: "Local" 323 | scope: "Host" 324 | handler: "IsSelinux" 325 | - name: "IngressPolicy" 326 | title: "Workload has ingress policy configured" 327 | shortDescription: "The Kubernetes network policy allows specific workloads or specific external IP addresses (such as an external Load Balancer) to access the application running" 328 | description: "An ingress network policy can prevent a workload from being leveraged to perform lateral movement and data ex-filtration" 329 | confidentiality: "Low" 330 | confidentialityDescription: "An ingress policy cuts down on accidental exposure to the Internet, which can lead to confidential data being leaked. (Accidental exposure can be caused when a Load Balancer, Node Port or Ingress Controller is added or misconfigured" 331 | integrity: "Low" 332 | integrityDescription: "An ingress policy cuts down on accidental exposure to the Internet, which can make vulnerable code or third-party processes available to be exploited by external attackers" 333 | availability: "High" 334 | availabilityDescription: "An ingress policy helps limit accidental exposure to the Internet, which can make workloads susceptible to DoS attacks from random attackers" 335 | exploitability: "None" 336 | attackVector: "Remote" 337 | scope: "None" 338 | handler: "IsIngressPolicy" 339 | - name: "EgressPolicy" 340 | title: "Workload has egress policy configured" 341 | shortDescription: "The Kubernetes network policy allows workloads to communicate with specific workloads or specific external IP addresses" 342 | description: "The Kubernetes egress network policy only allows workloads to communicate with specific workloads or specific external IP addresses, which reduces the attack surface" 343 | confidentiality: "High" 344 | confidentialityDescription: "A Kubernetes egress policy makes it harder for an attacker to exploit a vulnerable application or OS, or compromised third-party library, etc. to move laterally inside the cluster or exfiltrate confidential data" 345 | integrity: "Low" 346 | integrityDescription: "An egress policy makes it harder to leverage a compromised workload to attack other services in the cluster" 347 | availability: "Low" 348 | availabilityDescription: "An egress policy makes it more difficult for a workload to be leveraged to mount a DoS attack on other internal services in the cluster" 349 | exploitability: "None" 350 | attackVector: "Remote" 351 | scope: "Cluster" 352 | handler: "IsEgressPolicy" 353 | - name: "notListeningToContainerPorts" 354 | title: "A listening port isn’t configured" 355 | shortDescription: "A workload with no listening service is not susceptible to remote networking attacks" 356 | description: "A workload with no listening service is not susceptible to remote networking attacks" 357 | confidentiality: "High" 358 | confidentialityDescription: "When there is no listening port configured, workloads are not accessible remotely and are less likely to be leveraged for lateral movement and data exfiltration" 359 | integrity: "High" 360 | integrityDescription: "When there is no listening port, workloads with local vulnerabilities are less likely to be exploited" 361 | availability: "High" 362 | availabilityDescription: "When there is no listening port, workloads not accessible remotely are less likely to be overloaded by external users" 363 | exploitability: "None" 364 | attackVector: "Remote" 365 | scope: "None" 366 | handler: "IsNotListeningToContainerPorts" 367 | - name: "instrumentedByOctarine" 368 | title: "Workload is instrumented by Octarine" 369 | shortDescription: "Service meshes such as Istio and Octarine provide encryption of network traffic as well as strong identity, preventing network sniffing or Man-in-the-Middle (MiTM) attacks" 370 | description: "The Istio and Octarine service mesh encrypts all internal network activities with a mutual TLS connection and uses certificates to provide strong identity to all workloads, which greatly reduces the potential attack surface" 371 | confidentiality: "High" 372 | confidentialityDescription: "Service meshes, such as Istio and Octarine, provide encryption of network traffic, as well as strong identity, which prevents network sniffing and Man-in-the-Middle (MiTM) attacks" 373 | integrity: "Low" 374 | integrityDescription: "The strong identity provided by an Octarine and/or Istio service mesh prevents rogue containers from impersonating trusted workloads" 375 | availability: "Low" 376 | availabilityDescription: "Service meshes, such as Istio and Octarine, can detect and stop abnormal increases in network activities and network errors" 377 | exploitability: "None" 378 | attackVector: "Remote" 379 | scope: "None" 380 | handler: "IsInstrumentedByOctarine" 381 | - name: "instrumentedByIstio" 382 | title: "Workload is instrumented by Istio" 383 | shortDescription: "The Istio and Octarine service mesh encrypts all internal network activities with a mutual TLS connection and uses certificates to provide strong identity to all workloads, which greatly reduces the potential attack surface" 384 | description: "Service meshes such as Istio and Octarine provide encryption of network traffic as well as strong identity, preventing network sniffing or Man-in-the-Middle (MiTM) attacks" 385 | confidentiality: "High" 386 | confidentialityDescription: "Service meshes, such as Istio and Octarine, provide encryption of network traffic, as well as strong identity, which prevents network sniffing and Man-in-the-Middle (MiTM) attacks" 387 | integrity: "Low" 388 | integrityDescription: "The strong identity provided by an Octarine and/or Istio service mesh prevents rogue containers from impersonating trusted workloads" 389 | availability: "Low" 390 | availabilityDescription: "Service meshes, such as Istio and Octarine, can detect and stop abnormal increases in network activities and network errors" 391 | exploitability: "None" 392 | attackVector: "Remote" 393 | scope: "None" 394 | handler: "IsInstrumentedByIstio" 395 | --- 396 | apiVersion: v1 397 | kind: ServiceAccount 398 | metadata: 399 | name: kube-scan 400 | namespace: kube-scan 401 | labels: 402 | app: kube-scan 403 | --- 404 | apiVersion: rbac.authorization.k8s.io/v1 405 | kind: ClusterRole 406 | metadata: 407 | name: kube-scan 408 | labels: 409 | app: kube-scan 410 | rules: 411 | - apiGroups: 412 | - '' 413 | - 'rbac.authorization.k8s.io' 414 | - 'extensions' 415 | - 'apps' 416 | - 'batch' 417 | - 'networking.k8s.io' 418 | resources: 419 | - '*' 420 | verbs: 421 | - 'get' 422 | - 'list' 423 | - 'watch' 424 | --- 425 | apiVersion: rbac.authorization.k8s.io/v1 426 | kind: ClusterRoleBinding 427 | metadata: 428 | name: kube-scan 429 | labels: 430 | app: kube-scan 431 | roleRef: 432 | apiGroup: rbac.authorization.k8s.io 433 | kind: ClusterRole 434 | name: kube-scan 435 | subjects: 436 | - kind: ServiceAccount 437 | name: kube-scan 438 | namespace: kube-scan 439 | 440 | --- 441 | 442 | apiVersion: apps/v1 443 | kind: Deployment 444 | metadata: 445 | name: kube-scan 446 | namespace: kube-scan 447 | labels: 448 | app: kube-scan 449 | spec: 450 | selector: 451 | matchLabels: 452 | app: kube-scan 453 | template: 454 | metadata: 455 | labels: 456 | app: kube-scan 457 | spec: 458 | containers: 459 | - name: kube-scan-ui 460 | image: siddharth67/kubescan-scanner-ui 461 | imagePullPolicy: Always 462 | env: 463 | - name: API_SERVER_PORT 464 | value: "80" 465 | - name: CONTACT_LINK 466 | value: "mailto:info@octarinesec.com?subject=Octarine%20Contact%20Request" 467 | - name: WEBSITE_LINK 468 | value: "https://www.octarinesec.com" 469 | - name: kube-scan 470 | image: siddharth67/kubescan-scanner 471 | env: 472 | - name: KUBESCAN_PORT 473 | value: "80" 474 | - name: KUBESCAN_RISK_CONFIG_FILE_PATH 475 | value: "/etc/kubescan/risk-config.yaml" 476 | - name: KUBESCAN_REFRESH_STATE_INTERVAL_MINUTES 477 | value: "1440" 478 | imagePullPolicy: Always 479 | volumeMounts: 480 | - name: config 481 | mountPath: /etc/kubescan 482 | volumes: 483 | - name: config 484 | configMap: 485 | name: kube-scan 486 | defaultMode: 420 487 | serviceAccountName: kube-scan 488 | --- 489 | apiVersion: v1 490 | kind: Service 491 | metadata: 492 | name: kube-scan-ui 493 | namespace: kube-scan 494 | labels: 495 | app: kube-scan 496 | spec: 497 | ports: 498 | - name: kube-scan-ui 499 | port: 80 500 | protocol: TCP 501 | targetPort: 8080 502 | selector: 503 | app: kube-scan 504 | type: ClusterIP 505 | -------------------------------------------------------------------------------- /kubesec-scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #kubesec-scan.sh 4 | 5 | # using kubesec v2 api 6 | scan_result=$(curl -sSX POST --data-binary @"k8s_deployment_service.yaml" https://v2.kubesec.io/scan) 7 | scan_message=$(curl -sSX POST --data-binary @"k8s_deployment_service.yaml" https://v2.kubesec.io/scan | jq .[0].message -r ) 8 | scan_score=$(curl -sSX POST --data-binary @"k8s_deployment_service.yaml" https://v2.kubesec.io/scan | jq .[0].score ) 9 | 10 | 11 | # using kubesec docker image for scanning 12 | # scan_result=$(docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin < k8s_deployment_service.yaml) 13 | # scan_message=$(docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin < k8s_deployment_service.yaml | jq .[].message -r) 14 | # scan_score=$(docker run -i kubesec/kubesec:512c5e0 scan /dev/stdin < k8s_deployment_service.yaml | jq .[].score) 15 | 16 | 17 | # Kubesec scan result processing 18 | # echo "Scan Score : $scan_score" 19 | 20 | if [[ "${scan_score}" -ge 5 ]]; then 21 | echo "Score is $scan_score" 22 | echo "Kubesec Scan $scan_message" 23 | else 24 | echo "Score is $scan_score, which is less than or equal to 5." 25 | echo "Scanning Kubernetes Resource has Failed" 26 | exit 1; 27 | fi; -------------------------------------------------------------------------------- /opa-docker-security.rego: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | # Do Not store secrets in ENV variables 4 | secrets_env = [ 5 | "passwd", 6 | "password", 7 | "pass", 8 | "secret", 9 | "key", 10 | "access", 11 | "api_key", 12 | "apikey", 13 | "token", 14 | "tkn" 15 | ] 16 | 17 | deny[msg] { 18 | input[i].Cmd == "env" 19 | val := input[i].Value 20 | contains(lower(val[_]), secrets_env[_]) 21 | msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val]) 22 | } 23 | 24 | # Only use trusted base images 25 | #deny[msg] { 26 | # input[i].Cmd == "from" 27 | # val := split(input[i].Value[0], "/") 28 | # count(val) > 1 29 | # msg = sprintf("Line %d: use a trusted base image", [i]) 30 | #} 31 | 32 | # Do not use 'latest' tag for base imagedeny[msg] { 33 | deny[msg] { 34 | input[i].Cmd == "from" 35 | val := split(input[i].Value[0], ":") 36 | contains(lower(val[1]), "latest") 37 | msg = sprintf("Line %d: do not use 'latest' tag for base images", [i]) 38 | } 39 | 40 | # Avoid curl bashing 41 | deny[msg] { 42 | input[i].Cmd == "run" 43 | val := concat(" ", input[i].Value) 44 | matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1) 45 | count(matches) > 0 46 | msg = sprintf("Line %d: Avoid curl bashing", [i]) 47 | } 48 | 49 | # Do not upgrade your system packages 50 | upgrade_commands = [ 51 | "apk upgrade", 52 | "apt-get upgrade", 53 | "dist-upgrade", 54 | ] 55 | 56 | deny[msg] { 57 | input[i].Cmd == "run" 58 | val := concat(" ", input[i].Value) 59 | contains(val, upgrade_commands[_]) 60 | msg = sprintf("Line: %d: Do not upgrade your system packages", [i]) 61 | } 62 | 63 | # Do not use ADD if possible 64 | deny[msg] { 65 | input[i].Cmd == "add" 66 | msg = sprintf("Line %d: Use COPY instead of ADD", [i]) 67 | } 68 | 69 | # Any user... 70 | any_user { 71 | input[i].Cmd == "user" 72 | } 73 | 74 | deny[msg] { 75 | not any_user 76 | msg = "Do not run as root, use USER instead" 77 | } 78 | 79 | # ... but do not root 80 | forbidden_users = [ 81 | "root", 82 | "toor", 83 | "0" 84 | ] 85 | 86 | deny[msg] { 87 | input[i].Cmd == "user" 88 | val := input[i].Value 89 | contains(lower(val[_]), forbidden_users[_]) 90 | msg = sprintf("Line %d: Do not run as root: %s", [i, val]) 91 | } 92 | 93 | # Do not sudo 94 | deny[msg] { 95 | input[i].Cmd == "run" 96 | val := concat(" ", input[i].Value) 97 | contains(lower(val), "sudo") 98 | msg = sprintf("Line %d: Do not use 'sudo' command", [i]) 99 | } -------------------------------------------------------------------------------- /opa-k8s-security.rego: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | deny[msg] { 4 | input.kind = "Service" 5 | not input.spec.type = "NodePort" 6 | msg = "Service type should be NodePort" 7 | } 8 | 9 | deny[msg] { 10 | input.kind = "Deployment" 11 | not input.spec.template.spec.containers[0].securityContext.runAsNonRoot = true 12 | msg = "Containers must not run as root - use runAsNonRoot wihin container security context" 13 | } 14 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.5.RELEASE 9 | 10 | 11 | 12 | com.devsecops 13 | numeric 14 | 0.0.1 15 | numeric 16 | Demo for DevSecOps 17 | 18 | 19 | UTF-8 20 | UTF-8 21 | 1.8 22 | 9.0.43 23 | 5.4.4 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | org.springdoc 45 | springdoc-openapi-ui 46 | 1.2.30 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-security 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | 61 | 62 | 63 | org.jacoco 64 | jacoco-maven-plugin 65 | 0.8.5 66 | 67 | 68 | 69 | prepare-agent 70 | 71 | 72 | 73 | report 74 | test 75 | 76 | report 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.pitest 85 | pitest-maven 86 | 1.5.0 87 | 88 | 89 | org.pitest 90 | pitest-junit5-plugin 91 | 0.12 92 | 93 | 94 | 95 | 70 96 | 97 | XML 98 | HTML 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.owasp 106 | dependency-check-maven 107 | 6.1.6 108 | 109 | ALL 110 | 8 111 | 112 | 117 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /sec_files/file1: -------------------------------------------------------------------------------- 1 | username=sidd-harth 2 | -------------------------------------------------------------------------------- /sec_files/file3: -------------------------------------------------------------------------------- 1 | base64encodedsecret=cGFzc3dvcmQtaXMtcXdlcnR5MTIzCg== 2 | -------------------------------------------------------------------------------- /setup/azure-vm-template/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "value": "eastus" 7 | }, 8 | "networkInterfaceName": { 9 | "value": "devsecops-cloud801" 10 | }, 11 | "enableAcceleratedNetworking": { 12 | "value": true 13 | }, 14 | "networkSecurityGroupName": { 15 | "value": "devsecops-cloud-nsg" 16 | }, 17 | "networkSecurityGroupRules": { 18 | "value": [ 19 | { 20 | "name": "allow-all", 21 | "properties": { 22 | "priority": 100, 23 | "protocol": "*", 24 | "access": "Allow", 25 | "direction": "Inbound", 26 | "sourceApplicationSecurityGroups": [], 27 | "destinationApplicationSecurityGroups": [], 28 | "sourceAddressPrefix": "*", 29 | "sourcePortRange": "*", 30 | "destinationAddressPrefix": "*", 31 | "destinationPortRange": "*" 32 | } 33 | }, 34 | { 35 | "name": "default-allow-ssh", 36 | "properties": { 37 | "priority": 1000, 38 | "protocol": "TCP", 39 | "access": "Allow", 40 | "direction": "Inbound", 41 | "sourceApplicationSecurityGroups": [], 42 | "destinationApplicationSecurityGroups": [], 43 | "sourceAddressPrefix": "*", 44 | "sourcePortRange": "*", 45 | "destinationAddressPrefix": "*", 46 | "destinationPortRange": "22" 47 | } 48 | } 49 | ] 50 | }, 51 | "subnetName": { 52 | "value": "default" 53 | }, 54 | "virtualNetworkName": { 55 | "value": "devsecops-cloud_group-vnet" 56 | }, 57 | "addressPrefixes": { 58 | "value": [ 59 | "10.0.0.0/16" 60 | ] 61 | }, 62 | "subnets": { 63 | "value": [ 64 | { 65 | "name": "default", 66 | "properties": { 67 | "addressPrefix": "10.0.0.0/24" 68 | } 69 | } 70 | ] 71 | }, 72 | "publicIpAddressName": { 73 | "value": "devsecops-cloud-ip" 74 | }, 75 | "publicIpAddressType": { 76 | "value": "Static" 77 | }, 78 | "publicIpAddressSku": { 79 | "value": "Basic" 80 | }, 81 | "virtualMachineName": { 82 | "value": "devsecops-cloud" 83 | }, 84 | "virtualMachineComputerName": { 85 | "value": "devsecops-cloud" 86 | }, 87 | "virtualMachineRG": { 88 | "value": "devsecops-cloud_group" 89 | }, 90 | "osDiskType": { 91 | "value": "StandardSSD_LRS" 92 | }, 93 | "dataDisks": { 94 | "value": [ 95 | { 96 | "lun": 0, 97 | "createOption": "attach", 98 | "caching": "ReadOnly", 99 | "writeAcceleratorEnabled": false, 100 | "id": null, 101 | "name": "devsecops-cloud_DataDisk_0", 102 | "storageAccountType": null, 103 | "diskSizeGB": null, 104 | "diskEncryptionSet": null 105 | } 106 | ] 107 | }, 108 | "dataDiskResources": { 109 | "value": [ 110 | { 111 | "name": "devsecops-cloud_DataDisk_0", 112 | "sku": "StandardSSD_LRS", 113 | "properties": { 114 | "diskSizeGB": 512, 115 | "creationData": { 116 | "createOption": "empty" 117 | } 118 | } 119 | } 120 | ] 121 | }, 122 | "virtualMachineSize": { 123 | "value": "Standard_D4s_v3" 124 | }, 125 | "adminUsername": { 126 | "value": "devsecops" 127 | }, 128 | "adminPassword": { 129 | "value": null 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /setup/azure-vm-template/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "type": "string" 7 | }, 8 | "networkInterfaceName": { 9 | "type": "string" 10 | }, 11 | "enableAcceleratedNetworking": { 12 | "type": "bool" 13 | }, 14 | "networkSecurityGroupName": { 15 | "type": "string" 16 | }, 17 | "networkSecurityGroupRules": { 18 | "type": "array" 19 | }, 20 | "subnetName": { 21 | "type": "string" 22 | }, 23 | "virtualNetworkName": { 24 | "type": "string" 25 | }, 26 | "addressPrefixes": { 27 | "type": "array" 28 | }, 29 | "subnets": { 30 | "type": "array" 31 | }, 32 | "publicIpAddressName": { 33 | "type": "string" 34 | }, 35 | "publicIpAddressType": { 36 | "type": "string" 37 | }, 38 | "publicIpAddressSku": { 39 | "type": "string" 40 | }, 41 | "virtualMachineName": { 42 | "type": "string" 43 | }, 44 | "virtualMachineComputerName": { 45 | "type": "string" 46 | }, 47 | "virtualMachineRG": { 48 | "type": "string" 49 | }, 50 | "osDiskType": { 51 | "type": "string" 52 | }, 53 | "dataDisks": { 54 | "type": "array" 55 | }, 56 | "dataDiskResources": { 57 | "type": "array" 58 | }, 59 | "virtualMachineSize": { 60 | "type": "string" 61 | }, 62 | "adminUsername": { 63 | "type": "string" 64 | }, 65 | "adminPassword": { 66 | "type": "secureString" 67 | } 68 | }, 69 | "variables": { 70 | "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", 71 | "vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]", 72 | "subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]" 73 | }, 74 | "resources": [ 75 | { 76 | "name": "[parameters('networkInterfaceName')]", 77 | "type": "Microsoft.Network/networkInterfaces", 78 | "apiVersion": "2018-10-01", 79 | "location": "[parameters('location')]", 80 | "dependsOn": [ 81 | "[concat('Microsoft.Network/networkSecurityGroups/', parameters('networkSecurityGroupName'))]", 82 | "[concat('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]", 83 | "[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIpAddressName'))]" 84 | ], 85 | "properties": { 86 | "ipConfigurations": [ 87 | { 88 | "name": "ipconfig1", 89 | "properties": { 90 | "subnet": { 91 | "id": "[variables('subnetRef')]" 92 | }, 93 | "privateIPAllocationMethod": "Dynamic", 94 | "publicIpAddress": { 95 | "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName'))]" 96 | } 97 | } 98 | } 99 | ], 100 | "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", 101 | "networkSecurityGroup": { 102 | "id": "[variables('nsgId')]" 103 | } 104 | } 105 | }, 106 | { 107 | "name": "[parameters('networkSecurityGroupName')]", 108 | "type": "Microsoft.Network/networkSecurityGroups", 109 | "apiVersion": "2019-02-01", 110 | "location": "[parameters('location')]", 111 | "properties": { 112 | "securityRules": "[parameters('networkSecurityGroupRules')]" 113 | } 114 | }, 115 | { 116 | "name": "[parameters('virtualNetworkName')]", 117 | "type": "Microsoft.Network/virtualNetworks", 118 | "apiVersion": "2019-09-01", 119 | "location": "[parameters('location')]", 120 | "properties": { 121 | "addressSpace": { 122 | "addressPrefixes": "[parameters('addressPrefixes')]" 123 | }, 124 | "subnets": "[parameters('subnets')]" 125 | } 126 | }, 127 | { 128 | "name": "[parameters('publicIpAddressName')]", 129 | "type": "Microsoft.Network/publicIpAddresses", 130 | "apiVersion": "2019-02-01", 131 | "location": "[parameters('location')]", 132 | "properties": { 133 | "publicIpAllocationMethod": "[parameters('publicIpAddressType')]" 134 | }, 135 | "sku": { 136 | "name": "[parameters('publicIpAddressSku')]" 137 | } 138 | }, 139 | { 140 | "name": "[parameters('dataDiskResources')[copyIndex()].name]", 141 | "type": "Microsoft.Compute/disks", 142 | "apiVersion": "2020-09-30", 143 | "location": "[parameters('location')]", 144 | "properties": "[parameters('dataDiskResources')[copyIndex()].properties]", 145 | "sku": { 146 | "name": "[parameters('dataDiskResources')[copyIndex()].sku]" 147 | }, 148 | "copy": { 149 | "name": "managedDiskResources", 150 | "count": "[length(parameters('dataDiskResources'))]" 151 | } 152 | }, 153 | { 154 | "name": "[parameters('virtualMachineName')]", 155 | "type": "Microsoft.Compute/virtualMachines", 156 | "apiVersion": "2021-03-01", 157 | "location": "[parameters('location')]", 158 | "dependsOn": [ 159 | "managedDiskResources", 160 | "[concat('Microsoft.Network/networkInterfaces/', parameters('networkInterfaceName'))]" 161 | ], 162 | "properties": { 163 | "hardwareProfile": { 164 | "vmSize": "[parameters('virtualMachineSize')]" 165 | }, 166 | "storageProfile": { 167 | "osDisk": { 168 | "createOption": "fromImage", 169 | "managedDisk": { 170 | "storageAccountType": "[parameters('osDiskType')]" 171 | } 172 | }, 173 | "imageReference": { 174 | "publisher": "Canonical", 175 | "offer": "0001-com-ubuntu-server-jammy", 176 | "sku": "22_04-lts-gen2", 177 | "version": "latest" 178 | }, 179 | "copy": [ 180 | { 181 | "name": "dataDisks", 182 | "count": "[length(parameters('dataDisks'))]", 183 | "input": { 184 | "lun": "[parameters('dataDisks')[copyIndex('dataDisks')].lun]", 185 | "createOption": "[parameters('dataDisks')[copyIndex('dataDisks')].createOption]", 186 | "caching": "[parameters('dataDisks')[copyIndex('dataDisks')].caching]", 187 | "diskSizeGB": "[parameters('dataDisks')[copyIndex('dataDisks')].diskSizeGB]", 188 | "managedDisk": { 189 | "id": "[coalesce(parameters('dataDisks')[copyIndex('dataDisks')].id, if(equals(parameters('dataDisks')[copyIndex('dataDisks')].name, json('null')), json('null'), resourceId('Microsoft.Compute/disks', parameters('dataDisks')[copyIndex('dataDisks')].name)))]", 190 | "storageAccountType": "[parameters('dataDisks')[copyIndex('dataDisks')].storageAccountType]" 191 | }, 192 | "writeAcceleratorEnabled": "[parameters('dataDisks')[copyIndex('dataDisks')].writeAcceleratorEnabled]" 193 | } 194 | } 195 | ] 196 | }, 197 | "networkProfile": { 198 | "networkInterfaces": [ 199 | { 200 | "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]" 201 | } 202 | ] 203 | }, 204 | "osProfile": { 205 | "computerName": "[parameters('virtualMachineComputerName')]", 206 | "adminUsername": "[parameters('adminUsername')]", 207 | "adminPassword": "[parameters('adminPassword')]", 208 | "linuxConfiguration": { 209 | "patchSettings": { 210 | "patchMode": "ImageDefault" 211 | } 212 | } 213 | }, 214 | "diagnosticsProfile": { 215 | "bootDiagnostics": { 216 | "enabled": true 217 | } 218 | } 219 | } 220 | } 221 | ], 222 | "outputs": { 223 | "adminUsername": { 224 | "type": "string", 225 | "value": "[parameters('adminUsername')]" 226 | } 227 | } 228 | } -------------------------------------------------------------------------------- /setup/gcp-vm-gcloud-cmd/gcloud-cmds.md: -------------------------------------------------------------------------------- 1 | #Set Compute Zone 2 | gcloud config set compute/zone us-central1-a 3 | 4 | #Create ALLOW ALL Ingress Rule 5 | gcloud compute firewall-rules create allow-all \ 6 | --direction=INGRESS \ 7 | --priority=1000 \ 8 | --network=default \ 9 | --action=ALLOW \ 10 | --rules=all \ 11 | --source-ranges=0.0.0.0/0 \ 12 | --target-tags=allow-all 13 | 14 | #Create a Static IP to use it as a GCE External IP Address 15 | gcloud compute addresses create static-ip --region=us-central1 16 | 17 | #Fetch the Static IP 18 | gcloud compute addresses describe static-ip --region=us-central1 19 | 20 | #Create GCE Instance 21 | gcloud compute instances create devsecops-cloud --zone=us-central1-a \ 22 | --image=ubuntu-1804-bionic-v20210514 \ 23 | --image-project=ubuntu-os-cloud \ 24 | --machine-type=e2-standard-4 \ 25 | --address= \ 26 | --network-tier=PREMIUM \ 27 | --boot-disk-size=512GB \ 28 | --tags=allow-all 29 | 30 | gcloud compute ssh dev-cloud -------------------------------------------------------------------------------- /setup/jenkins-plugins/installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | JENKINS_URL='http://localhost:8080' 6 | 7 | JENKINS_CRUMB=$(curl -s --cookie-jar /tmp/cookies -u admin:admin ${JENKINS_URL}/crumbIssuer/api/json | jq .crumb -r) 8 | 9 | JENKINS_TOKEN=$(curl -s -X POST -H "Jenkins-Crumb:${JENKINS_CRUMB}" --cookie /tmp/cookies "${JENKINS_URL}/me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken?newTokenName=demo-token66" -u admin:admin | jq .data.tokenValue -r) 10 | 11 | echo $JENKINS_URL 12 | echo $JENKINS_CRUMB 13 | echo $JENKINS_TOKEN 14 | 15 | while read plugin; do 16 | echo "........Installing ${plugin} .." 17 | curl -s POST --data "" -H 'Content-Type: text/xml' "$JENKINS_URL/pluginManager/installNecessaryPlugins" --user "admin:$JENKINS_TOKEN" 18 | done < plugins.txt 19 | 20 | 21 | #### we also need to do a restart for some plugins 22 | 23 | #### check all plugins installed in jenkins 24 | # 25 | # http:///script 26 | 27 | # Jenkins.instance.pluginManager.plugins.each{ 28 | # plugin -> 29 | # println ("${plugin.getDisplayName()} (${plugin.getShortName()}): ${plugin.getVersion()}") 30 | # } 31 | 32 | 33 | #### Check for updates/errors - http:///updateCenter 34 | -------------------------------------------------------------------------------- /setup/jenkins-plugins/plugins.txt: -------------------------------------------------------------------------------- 1 | performance@3.18 2 | docker-workflow@1.26 3 | dependency-check-jenkins-plugin@5.1.1 4 | blueocean@1.24.7 5 | jacoco@3.2.0 6 | slack@2.4.8 7 | sonar@2.13.1 8 | pitmutation@1.0-18 9 | kubernetes-cli@1.10.2 -------------------------------------------------------------------------------- /setup/vm-install-script/install-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo ".........----------------#################._.-.-INSTALL-.-._.#################----------------........." 4 | PS1='\[\e[01;36m\]\u\[\e[01;37m\]@\[\e[01;33m\]\H\[\e[01;37m\]:\[\e[01;32m\]\w\[\e[01;37m\]\$\[\033[0;37m\] ' 5 | echo "PS1='\[\e[01;36m\]\u\[\e[01;37m\]@\[\e[01;33m\]\H\[\e[01;37m\]:\[\e[01;32m\]\w\[\e[01;37m\]\$\[\033[0;37m\] '" >> ~/.bashrc 6 | sed -i '1s/^/force_color_prompt=yes\n/' ~/.bashrc 7 | source ~/.bashrc 8 | 9 | # Don't ask to restart services after apt update, just do it. 10 | [ -f /etc/needrestart/needrestart.conf ] && sed -i 's/#\$nrconf{restart} = \x27i\x27/$nrconf{restart} = \x27a\x27/' /etc/needrestart/needrestart.conf 11 | 12 | apt-get autoremove -y #removes the packages that are no longer needed 13 | apt-get update 14 | systemctl daemon-reload 15 | 16 | KUBE_LATEST=$(curl -L -s https://dl.k8s.io/release/stable.txt | awk 'BEGIN { FS="." } { printf "%s.%s", $1, $2 }') 17 | mkdir -p /etc/apt/keyrings 18 | curl -fsSL https://pkgs.k8s.io/core:/stable:/${KUBE_LATEST}/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg 19 | echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${KUBE_LATEST}/deb/ /" >> /etc/apt/sources.list.d/kubernetes.list 20 | 21 | apt-get update 22 | KUBE_VERSION=$(apt-cache madison kubeadm | head -1 | awk '{print $3}') 23 | apt-get install -y docker.io vim build-essential jq python3-pip kubelet kubectl kubernetes-cni kubeadm containerd 24 | pip3 install jc 25 | 26 | ### UUID of VM 27 | ### comment below line if this Script is not executed on Cloud based VMs 28 | jc dmidecode | jq .[1].values.uuid -r 29 | 30 | systemctl enable kubelet 31 | 32 | echo ".........----------------#################._.-.-KUBERNETES-.-._.#################----------------........." 33 | rm -f /root/.kube/config 34 | kubeadm reset -f 35 | 36 | mkdir -p /etc/containerd 37 | containerd config default | sed 's/SystemdCgroup = false/SystemdCgroup = true/' > /etc/containerd/config.toml 38 | systemctl restart containerd 39 | 40 | # uncomment below line if your host doesnt have minimum requirement of 2 CPU 41 | # kubeadm init --pod-network-cidr '10.244.0.0/16' --service-cidr '10.96.0.0/16' --ignore-preflight-errors=NumCPU --skip-token-print 42 | kubeadm init --pod-network-cidr '10.244.0.0/16' --service-cidr '10.96.0.0/16' --skip-token-print 43 | 44 | mkdir -p ~/.kube 45 | cp -i /etc/kubernetes/admin.conf ~/.kube/config 46 | 47 | kubectl apply -f "https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s-1.11.yaml" 48 | kubectl rollout status daemonset weave-net -n kube-system --timeout=90s 49 | sleep 5 50 | 51 | echo "untaint controlplane node" 52 | node=$(kubectl get nodes -o=jsonpath='{.items[0].metadata.name}') 53 | for taint in $(kubectl get node $node -o jsonpath='{range .spec.taints[*]}{.key}{":"}{.effect}{"-"}{end}') 54 | do 55 | kubectl taint node $node $taint 56 | done 57 | kubectl get nodes -o wide 58 | 59 | echo ".........----------------#################._.-.-Docker-.-._.#################----------------........." 60 | 61 | cat > /etc/docker/daemon.json < /etc/apt/sources.list.d/jenkins.list 83 | apt update 84 | apt install -y jenkins 85 | systemctl daemon-reload 86 | systemctl enable jenkins 87 | systemctl start jenkins 88 | usermod -a -G docker jenkins 89 | echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 90 | 91 | echo ".........----------------#################._.-.-COMPLETED-.-._.#################----------------........." 92 | -------------------------------------------------------------------------------- /slack-emojis/deadpool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/deadpool.png -------------------------------------------------------------------------------- /slack-emojis/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/github.png -------------------------------------------------------------------------------- /slack-emojis/hulk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/hulk.jpg -------------------------------------------------------------------------------- /slack-emojis/jenkins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/jenkins.png -------------------------------------------------------------------------------- /slack-emojis/k8s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/k8s.png -------------------------------------------------------------------------------- /slack-emojis/ww.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodekloudhub/devsecops/ba02940c13eaf5390a46aced8ad4f4ab1457cb27/slack-emojis/ww.png -------------------------------------------------------------------------------- /src/main/java/com/devsecops/NumericApplication.java: -------------------------------------------------------------------------------- 1 | package com.devsecops; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class NumericApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(NumericApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/devsecops/NumericController.java: -------------------------------------------------------------------------------- 1 | package com.devsecops; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | @RestController 12 | public class NumericController { 13 | 14 | private final Logger logger = LoggerFactory.getLogger(getClass()); 15 | private static final String baseURL = "http://node-service:5000/plusone"; 16 | //private static final String baseURL = "http://localhost:5000/plusone"; 17 | 18 | RestTemplate restTemplate = new RestTemplate(); 19 | 20 | @RestController 21 | public class compare { 22 | 23 | @GetMapping("/") 24 | public String welcome() { 25 | return "Kubernetes DevSecOps"; 26 | } 27 | 28 | @GetMapping("/compare/{value}") 29 | public String compareToFifty(@PathVariable int value) { 30 | String message = "Could not determine comparison"; 31 | if (value > 50) { 32 | message = "Greater than 50"; 33 | } else { 34 | message = "Smaller than or equal to 50"; 35 | } 36 | return message; 37 | } 38 | 39 | @GetMapping("/increment/{value}") 40 | public int increment(@PathVariable int value) { 41 | ResponseEntity responseEntity = restTemplate.getForEntity(baseURL + '/' + value, String.class); 42 | String response = responseEntity.getBody(); 43 | logger.info("Value Received in Request - " + value); 44 | logger.info("Node Service Response - " + response); 45 | return Integer.parseInt(response); 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/java/com/devsecops/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.devsecops; 2 | 3 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 4 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | @EnableWebSecurity 8 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 9 | 10 | @Override 11 | protected void configure(HttpSecurity http) throws Exception { 12 | http.csrf().disable(); 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | random.service.url=http://node-service:5000/plusone 3 | -------------------------------------------------------------------------------- /src/test/java/com/devsecops/NumericApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.devsecops; 2 | 3 | 4 | import org.junit.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 14 | //import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | //import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.springframework.test.context.junit4.SpringRunner; 27 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 28 | 29 | @RunWith(SpringRunner.class) 30 | @SpringBootTest 31 | @AutoConfigureMockMvc 32 | public class NumericApplicationTests { 33 | 34 | @Autowired 35 | private MockMvc mockMvc; 36 | 37 | @Test 38 | public void smallerThanOrEqualToFiftyMessage() throws Exception { 39 | this.mockMvc.perform(get("/compare/50")).andDo(print()).andExpect(status().isOk()) 40 | .andExpect(content().string("Smaller than or equal to 50")); 41 | } 42 | 43 | @Test 44 | public void greaterThanFiftyMessage() throws Exception { 45 | this.mockMvc.perform(get("/compare/51")).andDo(print()).andExpect(status().isOk()) 46 | .andExpect(content().string("Greater than 50")); 47 | } 48 | 49 | 50 | 51 | @Test 52 | public void welcomeMessage() throws Exception { 53 | this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()) 54 | .andExpect(content().string("Kubernetes DevSecOps")); 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /trivy-docker-image-scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dockerImageName=$(awk 'NR==1 {print $2}' Dockerfile) 4 | echo $dockerImageName 5 | 6 | docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 0 --severity HIGH --light $dockerImageName 7 | docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 1 --severity CRITICAL --light $dockerImageName 8 | 9 | # Trivy scan result processing 10 | exit_code=$? 11 | echo "Exit Code : $exit_code" 12 | 13 | # Check scan results 14 | if [[ "${exit_code}" == 1 ]]; then 15 | echo "Image scanning failed. Vulnerabilities found" 16 | exit 1; 17 | else 18 | echo "Image scanning passed. No CRITICAL vulnerabilities found" 19 | fi; -------------------------------------------------------------------------------- /trivy-k8s-scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #trivy-k8s-scan 3 | 4 | echo $imageName #getting Image name from env variable 5 | 6 | docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 0 --severity LOW,MEDIUM,HIGH --light $imageName 7 | docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 1 --severity CRITICAL --light $imageName 8 | 9 | # Trivy scan result processing 10 | exit_code=$? 11 | echo "Exit Code : $exit_code" 12 | 13 | # Check scan results 14 | if [[ ${exit_code} == 1 ]]; then 15 | echo "Image scanning failed. Vulnerabilities found" 16 | exit 1; 17 | else 18 | echo "Image scanning passed. No vulnerabilities found" 19 | fi; -------------------------------------------------------------------------------- /vars/sendNotification.groovy: -------------------------------------------------------------------------------- 1 | def call(String buildStatus = 'STARTED') { 2 | buildStatus = buildStatus ?: 'SUCCESS' 3 | 4 | def color 5 | 6 | if (buildStatus == 'SUCCESS') { 7 | color = '#47ec05' 8 | emoji = ':ww:' 9 | } else if (buildStatus == 'UNSTABLE') { 10 | color = '#d5ee0d' 11 | emoji = ':deadpool:' 12 | } else { 13 | color = '#ec2805' 14 | emoji = ':hulk:' 15 | } 16 | 17 | // def msg = "${buildStatus}: `${env.JOB_NAME}` #${env.BUILD_NUMBER}:\n${env.BUILD_URL}" 18 | 19 | // slackSend(color: color, message: msg) 20 | 21 | attachments = [ 22 | [ 23 | "color": color, 24 | "blocks": [ 25 | [ 26 | "type": "header", 27 | "text": [ 28 | "type": "plain_text", 29 | "text": "K8S Deployment - ${deploymentName} Pipeline ${env.emoji}", 30 | "emoji": true 31 | ] 32 | ], 33 | [ 34 | "type": "section", 35 | "fields": [ 36 | [ 37 | "type": "mrkdwn", 38 | "text": "*Job Name:*\n${env.JOB_NAME}" 39 | ], 40 | [ 41 | "type": "mrkdwn", 42 | "text": "*Build Number:*\n${env.BUILD_NUMBER}" 43 | ] 44 | ], 45 | "accessory": [ 46 | "type": "image", 47 | "image_url": "https://raw.githubusercontent.com/sidd-harth/devsecops-k8s-demo/main/slack-emojis/jenkins.png", 48 | "alt_text": "Slack Icon" 49 | ] 50 | ], 51 | [ 52 | "type": "section", 53 | "text": [ 54 | "type": "mrkdwn", 55 | "text": "*Failed Stage Name: * `${env.failedStage}`" 56 | ], 57 | "accessory": [ 58 | "type": "button", 59 | "text": [ 60 | "type": "plain_text", 61 | "text": "Jenkins Build URL", 62 | "emoji": true 63 | ], 64 | "value": "click_me_123", 65 | "url": "${env.BUILD_URL}", 66 | "action_id": "button-action" 67 | ] 68 | ], 69 | [ 70 | "type": "divider" 71 | ], 72 | [ 73 | "type": "section", 74 | "fields": [ 75 | [ 76 | "type": "mrkdwn", 77 | "text": "*Kubernetes Deployment Name:*\n${deploymentName}" 78 | ], 79 | [ 80 | "type": "mrkdwn", 81 | "text": "*Node Port*\n32564" 82 | ] 83 | ], 84 | "accessory": [ 85 | "type": "image", 86 | "image_url": "https://raw.githubusercontent.com/sidd-harth/devsecops-k8s-demo/main/slack-emojis/k8s.png", 87 | "alt_text": "Kubernetes Icon" 88 | ], 89 | ], 90 | 91 | [ 92 | "type": "section", 93 | "text": [ 94 | "type": "mrkdwn", 95 | "text": "*Kubernetes Node: * `controlplane`" 96 | ], 97 | "accessory": [ 98 | "type": "button", 99 | "text": [ 100 | "type": "plain_text", 101 | "text": "Application URL", 102 | "emoji": true 103 | ], 104 | "value": "click_me_123", 105 | "url": "${applicationURL}:32564", 106 | "action_id": "button-action" 107 | ] 108 | ], 109 | [ 110 | "type": "divider" 111 | ], 112 | [ 113 | "type": "section", 114 | "fields": [ 115 | [ 116 | "type": "mrkdwn", 117 | "text": "*Git Commit:*\n${GIT_COMMIT}" 118 | ], 119 | [ 120 | "type": "mrkdwn", 121 | "text": "*GIT Previous Success Commit:*\n${GIT_PREVIOUS_SUCCESSFUL_COMMIT}" 122 | ] 123 | ], 124 | "accessory": [ 125 | "type": "image", 126 | "image_url": "https://raw.githubusercontent.com/sidd-harth/devsecops-k8s-demo/main/slack-emojis/github.png", 127 | "alt_text": "Github Icon" 128 | ] 129 | ], 130 | [ 131 | "type": "section", 132 | "text": [ 133 | "type": "mrkdwn", 134 | "text": "*Git Branch: * `${GIT_BRANCH}`" 135 | ], 136 | "accessory": [ 137 | "type": "button", 138 | "text": [ 139 | "type": "plain_text", 140 | "text": "Github Repo URL", 141 | "emoji": true 142 | ], 143 | "value": "click_me_123", 144 | "url": "${env.GIT_URL}", 145 | "action_id": "button-action" 146 | ] 147 | ] 148 | ] 149 | ] 150 | ] 151 | 152 | slackSend(iconEmoji: emoji, attachments: attachments) 153 | 154 | } -------------------------------------------------------------------------------- /zap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PORT=$(kubectl -n default get svc ${serviceName} -o json | jq .spec.ports[].nodePort) 4 | 5 | # first run this 6 | chmod 777 $(pwd) 7 | echo $(id -u):$(id -g) 8 | # docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-weekly zap-api-scan.py -t $applicationURL:$PORT/v3/api-docs -f openapi -r zap_report.html 9 | 10 | 11 | # comment above cmd and uncomment below lines to run with CUSTOM RULES 12 | docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-weekly zap-api-scan.py -t $applicationURL:$PORT/v3/api-docs -f openapi -c zap_rules -r zap_report.html 13 | 14 | exit_code=$? 15 | 16 | 17 | # HTML Report 18 | sudo mkdir -p owasp-zap-report 19 | sudo mv zap_report.html owasp-zap-report 20 | 21 | 22 | echo "Exit Code : $exit_code" 23 | 24 | if [[ ${exit_code} -ne 0 ]]; then 25 | echo "OWASP ZAP Report has either Low/Medium/High Risk. Please check the HTML Report" 26 | exit 1; 27 | else 28 | echo "OWASP ZAP did not report any Risk" 29 | fi; 30 | 31 | 32 | # Generate ConfigFile 33 | # docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-weekly zap-api-scan.py -t http://devsecops-demo.eastus.cloudapp.azure.com:31933/v3/api-docs -f openapi -g gen_file -------------------------------------------------------------------------------- /zap_rules: -------------------------------------------------------------------------------- 1 | # zap-api-scan rule configuration file 2 | # Change WARN to IGNORE to ignore rule or FAIL to fail if rule matches 3 | # Active scan rules set to IGNORE will not be run which will speed up the scan 4 | # Only the rule identifiers are used - the names are just for info 5 | # You can add your own messages to each rule by appending them after a tab on each line. 6 | 100001 IGNORE http://devsecops-demo.eastus.cloudapp.azure.com:31933/ 7 | 100000 IGNORE http://devsecops-demo.eastus.cloudapp.azure.com:31933/ 8 | --------------------------------------------------------------------------------