diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 000000000..13566b81b
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..6ed36dd36
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..35eb1ddfb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.kubernetes/create_aks.ps1 b/.kubernetes/create_aks.ps1
new file mode 100644
index 000000000..a3b3f8e8e
--- /dev/null
+++ b/.kubernetes/create_aks.ps1
@@ -0,0 +1,33 @@
+# Login to Azure (Uncomment the next line if you need to login or switch accounts)
+# az login
+
+# Set Variables
+$resourceGroupName = "devsecops"
+$location = "northeurope"
+$aksClusterName = "Devsecops-aks"
+$aksVersion = "1.29.0"
+
+# Create Resource Group if it doesn't exist
+$resourceGroupExists = az group exists --name $resourceGroupName
+if (-not $resourceGroupExists) {
+ Write-Output "Creating resource group '$resourceGroupName' in location '$location'."
+ az group create --name $resourceGroupName --location $location
+} else {
+ Write-Output "Resource group '$resourceGroupName' already exists."
+}
+
+# Create AKS Cluster
+Write-Output "Creating AKS cluster named '$aksClusterName' in resource group '$resourceGroupName'."
+az aks create `
+ --resource-group $resourceGroupName `
+ --name $aksClusterName `
+ --node-count 1 `
+ --enable-addons monitoring `
+ --kubernetes-version $aksVersion `
+ --generate-ssh-keys `
+ --location $location `
+ --node-vm-size Standard_B2s `
+ --load-balancer-sku basic `
+ --no-wait
+
+Write-Output "AKS cluster creation command has been executed. It may take a few minutes for the cluster to be fully operational."
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 000000000..97606ffb5
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+ Default
+ python interpreter
+
+ /${PROJECT_DIR_NAME}/devops-spring-project
+
+
diff --git a/.talismanrc b/.talismanrc
new file mode 100644
index 000000000..2fda1b123
--- /dev/null
+++ b/.talismanrc
@@ -0,0 +1,4 @@
+fileignoreconfig:
+- filename: Jenkinsfile
+ checksum: a4c9cd41b0402afd7b2324cc30a6628cbd0aade31a0d852ef2664b297e2bc117
+version: ""
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..e01206508
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.compile.nullAnalysis.mode": "automatic",
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 6eea1d93c..f0551cc89 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,7 @@
FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/*.jar
-ADD ${JAR_FILE} app.jar
-ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
+RUN addgroup -S pipeline && adduser -S k8s-pipeline -G pipeline
+COPY ${JAR_FILE} /home/k8s-pipeline/app.jar
+USER k8s-pipeline
+ENTRYPOINT ["java","-jar","/home/k8s-pipeline/app.jar"]
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 56745c21a..ecf314a41 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,12 +1,194 @@
pipeline {
- agent any
+ agent any
- stages {
- stage('Build Artifact') {
+ environment {
+ // Set JVM options for Maven
+ deploymentName = "devsecops"
+ containerName = "devsecops-container"
+ serviceName = "devsecops-svc"
+ imageName = "manlikeabz/numeric-app:${GIT_COMMIT}"
+ applicationURL = "http://devsecops-abzconsultancies.eastus.cloudapp.azure.com/"
+ applicationURI = "/increment/99"
+ MAVEN_OPTS = "--add-opens java.base/java.lang=ALL-UNNAMED"
+ AKS_CLUSTER_NAME = 'Devsecops-aks'
+ NAMESPACE = 'default'
+ }
+
+ stages {
+ stage('Build Artifact') {
+ steps {
+ // Use environment variable for Maven options
+ sh 'mvn clean package -DskipTests=true'
+ archiveArtifacts artifacts: 'target/*.jar', onlyIfSuccessful: true
+ }
+ }
+
+ stage('Unit Tests - JUnit and Jacoco') {
+ steps {
+ sh "mvn test"
+ }
+ post {
+ always {
+ junit 'target/surefire-reports/*.xml'
+ jacoco execPattern: 'target/jacoco.exec'
+ }
+ }
+ }
+
+ stage('Mutation Tests - PIT') {
+ steps {
+ sh 'mvn org.pitest:pitest-maven:mutationCoverage'
+ }
+ post {
+ always {
+ pitmutation mutationStatsFile: '**/pit-reports/**/mutations.xml'
+ }
+ }
+ }
+
+ stage('SonarQube - SAST') {
+ steps {
+ withSonarQubeEnv('SonarQube') {
+ sh "mvn sonar:sonar -Dsonar.projectKey=numeric-application -Dsonar.host.url=http://devsecops-abzconsultancies.eastus.cloudapp.azure.com:9000 -Dsonar.token=squ_b0ffa602f401442384e12c06cbd73a66b51a7d2a"
+ // Make sure the SonarQube scanner has finished before proceeding
+ }
+ script {
+ // It will wait indefinitely for the SonarQube analysis to complete
+ def qg = waitForQualityGate()
+ if (qg.status != 'OK') {
+ error "Quality gate not passed: ${qg.status}"
+ }
+ }
+ }
+ }
+
+ stage('Vulnerability Scan - Docker') {
+ parallel {
+ stage('Maven Dependency Check') {
+ steps {
+ script {
+ sh "mvn dependency-check:check"
+ }
+ }
+ post {
+ always {
+ dependencyCheckPublisher pattern: 'target/dependency-check-report.xml'
+ }
+ }
+ }
+ stage('Trivy Scan') {
+ steps {
+ script {
+ sh "bash trivy-docker-image-scan.sh"
+ //trivy image --exit-code 0 --severity HIGH,CRITICAL manlikeabz/numeric-app:${GIT_COMMIT}
+ }
+ }
+ }
+ stage('OPA Conftest') {
+ steps {
+ script {
+ // Run OPA Conftest against the Dockerfile
+ sh 'docker run --rm -v $(pwd):/project openpolicyagent/conftest test --policy opa-docker-security.rego Dockerfile'
+ }
+ }
+ }
+ }
+ }
+
+ stage('Docker Build and Push') {
+ steps {
+ script {
+ // Ensure GIT_COMMIT is populated
+ GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
+ echo "Building and pushing Docker image for commit: ${GIT_COMMIT}"
+
+ // Docker login using credentials securely
+ withDockerRegistry([credentialsId: "90cf476e-ad01-40fe-86fa-4b0599ac41ff", url: ""]) {
+ sh "printenv"
+
+ // Docker build and push commands
+ sh "docker build -t manlikeabz/numeric-app:${GIT_COMMIT} ."
+ sh "docker push manlikeabz/numeric-app:${GIT_COMMIT}"
+ }
+ }
+ }
+ }
+
+ // stage('Vulneability Scan - Kubernetes') {
+ // steps {
+ // script {
+ // // Run OPA Conftest scan against the Kubernetes deployment file
+ // sh "docker run --rm -v $pwd:/project openpolicyagent/conftest test --policy opa-k8s-security.rego k8s_deployment_service.yaml"
+ // }
+ // }
+ // }
+
+ stage('Vulnerability Scan - Kubernetes') {
steps {
- sh "mvn clean package -DskipTests=true"
- archive 'target/*.jar' //so that they can be downloaded later
+ parallel(
+ "OPA Scan": {
+ script {
+ // Run OPA Conftest scan against the Kubernetes deployment file
+ sh "docker run --rm -v $(pwd):/project openpolicyagent/conftest test --policy opa-k8s-security.rego k8s_deployment_service.yaml"
+ }
+ },
+ "Kubesec Scan": {
+ sh "bsh kubesec-scan.sh"
+ }
+ }
+ )
}
- }
+ }
+
+ // stage('Kubernetes Deployment - DEV') {
+ // steps {
+ // withKubeConfig([credentialsId: 'kubeconfig']) {
+ // sh "kubectl config use-context ${AKS_CLUSTER_NAME}"
+ // sh "sed -i 's#replace#manlikeabz/numeric-app:${GIT_COMMIT}#g' k8s_deployment_service.yaml"
+ // sh "kubectl apply -f k8s_deployment_service.yaml"
+ // }
+ // }
+ // }
+
+ stage('Integration Tests - DEV') {
+ steps {
+ script {
+ try {
+ withKubeConfig([credentialsId: 'kubeconfig']) {
+ sh "bash integration-tests.sh"
+ }
+ } catch (Exception e) {
+ withKubeConfig([credentialsId: 'kubeconfig']) {
+ sh "kubectl rollout undo deployment ${deploymentName}"
+ }
+ throw e
+ }
+ }
+ }
+
+ stage('K8s Deployment - Dev'){
+ steps{
+ parallel(
+ "Deploynment": {
+ withKubeConfig([credentialsId: 'kubeconfig']) {
+ sh "bash k8s-deployment.sh"
+ }
+ },
+ "Rollout Status": {
+ withKubeConfig([credentialsId: 'kubeconfig']) {
+ sh "bash k8s-deployment-rollout-status.sh"
+ }
+ }
+ )
+ }
+ }
+ }
+
+ post {
+ always {
+ // Cleanup after Docker to avoid logged in credentials hanging around
+ sh "docker logout"
+ echo 'Pipeline execution complete.'
+ }
}
-}
\ No newline at end of file
+}
diff --git a/clusterrolebinding.yaml b/clusterrolebinding.yaml
new file mode 100644
index 000000000..700906d77
--- /dev/null
+++ b/clusterrolebinding.yaml
@@ -0,0 +1,14 @@
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: deployment-manager-binding
+ namespace: default
+subjects:
+- kind: ServiceAccount
+ name: jenkins-deployer
+ namespace: default
+roleRef:
+ kind: Role
+ name: deployment-manager
+ apiGroup: rbac.authorization.k8s.io
+
diff --git a/integration-test.sh b/integration-test.sh
new file mode 100644
index 000000000..4278b8819
--- /dev/null
+++ b/integration-test.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#integration-test.sh
+
+sleep 5s
+
+PORT=$(kubectl -n default get svc ${serviceName} -o json | jq .spec.ports[].nodePort)
+echo $PORT
+echo $applicationURL:$PORT/$applicationURI
+
+if [[ ! -z "$PORT" ]]
+then
+ response=$(curl -s $applicationURL:$PORT$applicationURI)
+ http_code=$(curl -s -o /dev/null -w "%{http_code}" $applicationURL:$PORT$applicationURI)
+
+ if [[ "$response" == 100 ]];
+ then
+ echo "Increment Test Passed"
+ else
+ echo "Increment Test Failed"
+ exit 1;
+ fi;
+
+ if [[ "$http_code" == 200 ]];
+ then
+ echo "HTTP Status Code Test Passed"
+ else
+ echo "HTTP Status code is not 200"
+ exit 1;
+ fi;
+else
+ echo "The Service does not have a NodePort"
+ exit 1;
+fi;
diff --git a/k8s-deployment-rollout-status.sh b/k8s-deployment-rollout-status.sh
new file mode 100644
index 000000000..f3fa22843
--- /dev/null
+++ b/k8s-deployment-rollout-status.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+sleep 60s
+
+if [[ $(kubectl -n default rollout status deployment/${deploymentName} --timeout 5s) != *"successfully rolled out"* ]];
+then
+ echo "Deployment ${deploymentName} rolled out has failed"
+ kubectl rollout undo deployment/${deploymentName} -n default
+else
+ echo "Deployment ${deploymentName} rollout success"
+fi
\ No newline at end of file
diff --git a/k8s-deployment.sh b/k8s-deployment.sh
new file mode 100644
index 000000000..88a62e0ef
--- /dev/null
+++ b/k8s-deployment.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+sed -i "s#replace#${imageName}#g" k8s_deployment_service.yaml
+# kubectl -n default get deployment ${deploymentName} &> /dev/null
+
+# if [[ $? -ne 0]]; then
+# echo "deployment ${deploymentName} not found, creating new deployment"
+# kubectl apply -f k8s_deployment_service.yaml -n default
+# else
+# echo "deployment ${deploymentName} found, updating deployment"
+# echo "image name - ${imageName}"
+# kubectl set image deployment/${deploymentName} ${containerName}=${imageName} -n default
+# fi
+
+kubectl apply -f k8s_deployment_service.yaml -n default
\ No newline at end of file
diff --git a/k8s_deployment_service.yaml b/k8s_deployment_service.yaml
index 574742d32..ee1b717fd 100644
--- a/k8s_deployment_service.yaml
+++ b/k8s_deployment_service.yaml
@@ -15,9 +15,27 @@ spec:
labels:
app: devsecops
spec:
+ volumes:
+ - name: vol
+ emptyDir: {}
+ serviceAccount: default
containers:
- image: replace
name: devsecops-container
+ volumeMounts:
+ - name: vol
+ mountPath: /tmp
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 100
+ readOnlyRootFilesystem: true
+ resources:
+ limits:
+ cpu: "1"
+ memory: "1Gi"
+ requests:
+ cpu: "100m"
+ memory: "256Mi"
---
apiVersion: v1
kind: Service
diff --git a/kubesec-scan.sh b/kubesec-scan.sh
new file mode 100644
index 000000000..5d78d777a
--- /dev/null
+++ b/kubesec-scan.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# using kubesec v2 api
+scan_result=$(curl -sSX POST --data-binary @k8s_deployment_service.yaml https://kubesec.io/api/v1/scan)
+scan_message=$(curl -sSX POST --data-binary @k8s_deployment_service.yaml https://kubesec.io/api/v1/scan | jq -r '.data[].message')
+scan_score=$(curl -sSX POST --data-binary @k8s_deployment_service.yaml https://kubesec.io/api/v1/scan | jq -r '.data[].score')
+
+if [[ $scan_score -ge 9 ]]; then
+ echo "Score is $scan_score"
+ echo "Kubesec scan message: $scan_message"
+ exit 1
+else
+ echo "Score is $scan_score, which is less than or equal to 5"
+ echo "Scanning Kubernetes Resource has failed"
+ exit 1;
+fi
\ No newline at end of file
diff --git a/opa-docker-security.rego b/opa-docker-security.rego
new file mode 100644
index 000000000..aa34c2c8e
--- /dev/null
+++ b/opa-docker-security.rego
@@ -0,0 +1,107 @@
+package main
+
+# Do Not store secrets in ENV variables
+secrets_env = [
+ "passwd",
+ "password",
+ "pass",
+ "secret",
+ "key",
+ "access",
+ "api_key",
+ "apikey",
+ "token",
+ "tkn"
+]
+
+deny[msg] {
+ input[i].Cmd == "env"
+ val := input[i].Value
+ contains(lower(val[_]), secrets_env[_])
+ msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val])
+}
+
+# Only use trusted base images
+deny[msg] {
+ input[i].Cmd == "from"
+ val := split(input[i].Value[0], "/")
+ count(val) > 1
+ msg = sprintf("Line %d: use a trusted base image", [i])
+}
+
+# Do not use 'latest' tag for base imagedeny[msg] {
+deny[msg] {
+ input[i].Cmd == "from"
+ val := split(input[i].Value[0], ":")
+ contains(lower(val[1]), "latest")
+ msg = sprintf("Line %d: do not use 'latest' tag for base images", [i])
+}
+
+# Avoid curl bashing
+deny[msg] {
+ input[i].Cmd == "run"
+ val := concat(" ", input[i].Value)
+ matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1)
+ count(matches) > 0
+ msg = sprintf("Line %d: Avoid curl bashing", [i])
+}
+
+# Do not upgrade your system packages
+warn[msg] {
+ input[i].Cmd == "run"
+ val := concat(" ", input[i].Value)
+ matches := regex.match(".*?(apk|yum|dnf|apt|pip).+?(install|[dist-|check-|group]?up[grade|date]).*", lower(val))
+ matches == true
+ msg = sprintf("Line: %d: Do not upgrade your system packages: %s", [i, val])
+}
+
+# Do not use ADD if possible
+deny[msg] {
+ input[i].Cmd == "add"
+ msg = sprintf("Line %d: Use COPY instead of ADD", [i])
+}
+
+# Any user...
+any_user {
+ input[i].Cmd == "user"
+ }
+
+deny[msg] {
+ not any_user
+ msg = "Do not run as root, use USER instead"
+}
+
+# ... but do not root
+forbidden_users = [
+ "root",
+ "toor",
+ "0"
+]
+
+deny[msg] {
+ command := "user"
+ users := [name | input[i].Cmd == "user"; name := input[i].Value]
+ lastuser := users[count(users)-1]
+ contains(lower(lastuser[_]), forbidden_users[_])
+ msg = sprintf("Line %d: Last USER directive (USER %s) is forbidden", [i, lastuser])
+}
+
+# Do not sudo
+deny[msg] {
+ input[i].Cmd == "run"
+ val := concat(" ", input[i].Value)
+ contains(lower(val), "sudo")
+ msg = sprintf("Line %d: Do not use 'sudo' command", [i])
+}
+
+# Use multi-stage builds
+default multi_stage = false
+multi_stage = true {
+ input[i].Cmd == "copy"
+ val := concat(" ", input[i].Flags)
+ contains(lower(val), "--from=")
+}
+deny[msg] {
+ multi_stage == false
+ msg = sprintf("You COPY, but do not appear to use multi-stage builds...", [])
+}
\ No newline at end of file
diff --git a/opa-k8s-security.rego b/opa-k8s-security.rego
new file mode 100644
index 000000000..a2d631e45
--- /dev/null
+++ b/opa-k8s-security.rego
@@ -0,0 +1,13 @@
+package main
+
+deny[msg] {
+ input.kind = "Service"
+ not input.spec.type == "NodePort"
+ msg = "Service type should be NodePort"
+}
+
+deny[msg] {
+ input.kind = "Deployment"
+ not input.spec.template.spec.containers[0].securityContext.runAsNonRoot == true
+ msg = "Containers must not run as root - use runAsNonRoot within the container security context"
+}
\ No newline at end of file
diff --git a/packages-microsoft-prod.deb b/packages-microsoft-prod.deb
new file mode 100644
index 000000000..14e3c6285
Binary files /dev/null and b/packages-microsoft-prod.deb differ
diff --git a/pom.xml b/pom.xml
index 8e71c7ac2..75ecb15c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,10 +3,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- org.springframework.boot
- spring-boot-starter-parent
- 2.2.1.RELEASE
-
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.3
com.devsecops
@@ -18,7 +17,7 @@
UTF-8
UTF-8
- 1.8
+ 17
@@ -32,6 +31,12 @@
spring-boot-starter
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.15.2
+
+
org.springframework.boot
spring-boot-starter-test
@@ -45,8 +50,54 @@
org.springframework.boot
spring-boot-maven-plugin
-
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.7
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+
+
+ org.pitest
+ pitest-maven
+ 1.7.0
+
+
+ org.pitest
+ pitest-junit5-plugin
+ 0.14
+
+
+
+ 70
+
+ HTML
+ XML
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.5.0
+
+ 20
+ ALL
+
+
-
-
+
\ No newline at end of file
diff --git a/role.yaml b/role.yaml
new file mode 100644
index 000000000..4a2c04e4b
--- /dev/null
+++ b/role.yaml
@@ -0,0 +1,10 @@
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ namespace: default
+ name: deployment-manager
+rules:
+- apiGroups: ["", "apps", "extensions"]
+ resources: ["deployments", "replicasets", "pods"]
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
+
diff --git a/secret.yaml b/secret.yaml
new file mode 100644
index 000000000..a41492622
--- /dev/null
+++ b/secret.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+type: kubernetes.io/service-account-token
+metadata:
+ name: token-secret
+ namespace: default # Replace with the actual namespace name
+ annotations:
+ kubernetes.io/service-account.name: "jenkins-deployer"
diff --git a/setup/vm-install-script/install-script.sh b/setup/vm-install-script/install-script.sh
index 1ccbf3bff..20aa6f1a3 100644
--- a/setup/vm-install-script/install-script.sh
+++ b/setup/vm-install-script/install-script.sh
@@ -15,9 +15,9 @@ cat < /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
-KUBE_VERSION=1.20.0
+KUBE_VERSION=1.28.0
apt-get update
-apt-get install -y docker.io vim build-essential jq python3-pip kubelet=${KUBE_VERSION}-00 kubectl=${KUBE_VERSION}-00 kubernetes-cni=0.8.7-00 kubeadm=${KUBE_VERSION}-00
+apt-get install -y docker.io vim build-essential jq python3-pip kubelet=${KUBE_VERSION}-00 kubectl=${KUBE_VERSION}-00 kubernetes-cni=1.2.0-00 kubeadm=${KUBE_VERSION}-00
pip3 install jc
### UUID of VM
@@ -62,7 +62,7 @@ kubectl get node -o wide
echo ".........----------------#################._.-.-Java and MAVEN-.-._.#################----------------........."
-sudo apt install openjdk-11-jdk -y
+sudo apt install openjdk-17-jdk -y
java -version
sudo apt install -y maven
mvn -v
@@ -70,10 +70,13 @@ mvn -v
echo ".........----------------#################._.-.-JENKINS-.-._.#################----------------........."
-wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
-sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
-sudo apt update
-sudo apt install -y jenkins
+sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
+ https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
+echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
+ https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
+ /etc/apt/sources.list.d/jenkins.list > /dev/null
+sudo apt-get update
+sudo apt-get install jenkins
systemctl daemon-reload
systemctl enable jenkins
sudo systemctl start jenkins
diff --git a/src/main/java/com/devsecops/NumericController.java b/src/main/java/com/devsecops/NumericController.java
index b0aeeeeed..7b31dd0ee 100644
--- a/src/main/java/com/devsecops/NumericController.java
+++ b/src/main/java/com/devsecops/NumericController.java
@@ -2,53 +2,51 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
-import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@RestController
-public class NumericController {
-
- private final Logger logger = LoggerFactory.getLogger(getClass());
- private static final String baseURL = "http://node-service:5000/plusone";
-
- RestTemplate restTemplate = new RestTemplate();
-
- @RestController
- public class compare {
-
- @GetMapping("/")
- public String welcome() {
- return "Kubernetes DevSecOps";
- }
-
- @GetMapping("/compare/{value}")
- public String compareToFifty(@PathVariable int value) {
- String message = "Could not determine comparison";
- if (value > 50) {
- message = "Greater than 50";
- } else {
- message = "Smaller than or equal to 50";
- }
- return message;
- }
-
- @GetMapping("/increment/{value}")
- public int increment(@PathVariable int value) {
- ResponseEntity responseEntity = restTemplate.getForEntity(baseURL + '/' + value, String.class);
- String response = responseEntity.getBody();
- logger.info("Value Received in Request - " + value);
- logger.info("Node Service Response - " + response);
- return Integer.parseInt(response);
- }
- }
-
-}
\ No newline at end of file
+class NumericController {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+ private static final String BASE_URL = "http://node-service:5000/plusone";
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ @RestController
+ class Compare {
+
+ @GetMapping("/")
+ String welcome() {
+ return "Kubernetes DevSecOps";
+ }
+
+ @GetMapping("/compare/{value}")
+ String compareToFifty(@PathVariable int value) {
+ return value > 50 ? "Greater than 50" : "Smaller than or equal to 50";
+ }
+
+ @GetMapping("/increment/{value}")
+ int increment(@PathVariable int value) {
+ try {
+ ResponseEntity responseEntity = restTemplate.getForEntity(BASE_URL + '/' + value, String.class);
+ String response = responseEntity.getBody();
+ logger.info("Value Received in Request - {}", value);
+ logger.info("Node Service Response - {}", response);
+ return Integer.parseInt(response);
+ } catch (NumberFormatException e) {
+ logger.error("Error parsing response to integer. Value received: {}, Error: {}", value, e.getMessage());
+ throw new ResponseParseException("Failed to parse the response from Node Service for value: " + value, e);
+ }
+ }
+ }
+
+ // Custom exception class for handling response parse errors
+ static class ResponseParseException extends RuntimeException {
+ ResponseParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/src/main/java/com/devsecops/ResponseParseException.java b/src/main/java/com/devsecops/ResponseParseException.java
new file mode 100644
index 000000000..13aafff56
--- /dev/null
+++ b/src/main/java/com/devsecops/ResponseParseException.java
@@ -0,0 +1,11 @@
+package com.devsecops;
+
+public class ResponseParseException extends RuntimeException {
+ public ResponseParseException(String message) {
+ super(message);
+ }
+
+ public ResponseParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/test/java/com/devsecops/NumericApplicationTests.java b/src/test/java/com/devsecops/NumericApplicationTests.java
index 3e0ae20a4..619d6b98c 100644
--- a/src/test/java/com/devsecops/NumericApplicationTests.java
+++ b/src/test/java/com/devsecops/NumericApplicationTests.java
@@ -1,55 +1,40 @@
package com.devsecops;
-
-import org.junit.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.web.servlet.MockMvc;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-//import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
-public class NumericApplicationTests {
+class NumericApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
- public void smallerThanOrEqualToFiftyMessage() throws Exception {
- this.mockMvc.perform(get("/compare/49")).andDo(print()).andExpect(status().isOk())
+ void smallerThanOrEqualToFiftyMessage() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/compare/50"))
+ .andExpect(status().isOk())
.andExpect(content().string("Smaller than or equal to 50"));
}
@Test
- public void greaterThanFiftyMessage() throws Exception {
- this.mockMvc.perform(get("/compare/51")).andDo(print()).andExpect(status().isOk())
+ void greaterThanFiftyMessage() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/compare/51"))
+ .andExpect(status().isOk())
.andExpect(content().string("Greater than 50"));
}
-
+
@Test
- public void welcomeMessage() throws Exception {
- this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk());
+ void welcomeMessage() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Kubernetes DevSecOps"));
}
-
-
-}
\ No newline at end of file
+}
diff --git a/trivy-docker-image-scan.sh b/trivy-docker-image-scan.sh
new file mode 100644
index 000000000..ff7b48212
--- /dev/null
+++ b/trivy-docker-image-scan.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+dockerImageName=$(awk 'NR==1 {print $2}' Dockerfile)
+echo $dockerImageName
+
+# Ensure Docker is logged in if the image is private
+echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
+
+# Run Trivy scan
+docker run --rm -v $WORKSPACE:/root/.cache/aquasec/trivy:0.51.1 aquasec/trivy:0.51.1 image --exit-code 0 --severity HIGH --light $dockerImageName
+docker run --rm -v $WORKSPACE:/root/.cache/aquasec/trivy:0.51.1 aquasec/trivy:0.51.1 image --exit-code 1 --severity CRITICAL --light $dockerImageName
+
+# Trivy scan result processing
+exit_code=$?
+
+echo "Exit Code : $exit_code"
+
+# Check scan results
+if [[ "${exit_code}" -eq 1 ]]; then
+ echo "Image scanning failed. Vulnerabilities found"
+ exit 1
+else
+ echo "Image scanning passed. No CRITICAL vulnerabilities found"
+fi
diff --git a/trivy-k8s-scan.sh b/trivy-k8s-scan.sh
new file mode 100644
index 000000000..3e6be0ea5
--- /dev/null
+++ b/trivy-k8s-scan.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#trivy-k8s-scan
+
+echo $imageName #getting Image name from env variable
+
+docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 0 --severity LOW,MEDIUM,HIGH --light $imageName
+docker run --rm -v $WORKSPACE:/root/.cache/ aquasec/trivy:0.17.2 -q image --exit-code 1 --severity CRITICAL --light $imageName
+
+# Trivy scan result processing
+exit_code=$?
+echo "Exit Code : $exit_code"
+
+# Check scan results
+if [[ ${exit_code} == 1 ]]; then
+ echo "Image scanning failed. Vulnerabilities found"
+ exit 1;
+else
+ echo "Image scanning passed. No vulnerabilities found"
+fi;
diff --git a/untitled/.gitignore b/untitled/.gitignore
new file mode 100644
index 000000000..5ff6309b7
--- /dev/null
+++ b/untitled/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/untitled/.idea/.gitignore b/untitled/.idea/.gitignore
new file mode 100644
index 000000000..13566b81b
--- /dev/null
+++ b/untitled/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/untitled/.idea/misc.xml b/untitled/.idea/misc.xml
new file mode 100644
index 000000000..690fc288b
--- /dev/null
+++ b/untitled/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/untitled/.idea/vcs.xml b/untitled/.idea/vcs.xml
new file mode 100644
index 000000000..6c0b86358
--- /dev/null
+++ b/untitled/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/untitled/pom.xml b/untitled/pom.xml
new file mode 100644
index 000000000..2f4e6dd6c
--- /dev/null
+++ b/untitled/pom.xml
@@ -0,0 +1,9 @@
+
+ 4.0.0
+ org.example
+ untitled
+ 1.0-SNAPSHOT
+ Archetype - untitled
+ http://maven.apache.org
+
diff --git a/untitled/src/main/resources/META-INF/maven/archetype.xml b/untitled/src/main/resources/META-INF/maven/archetype.xml
new file mode 100644
index 000000000..507c1c0d2
--- /dev/null
+++ b/untitled/src/main/resources/META-INF/maven/archetype.xml
@@ -0,0 +1,9 @@
+
+ untitled
+
+
+
+
+
+
+
diff --git a/untitled/src/main/resources/archetype-resources/pom.xml b/untitled/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 000000000..9c5e7e740
--- /dev/null
+++ b/untitled/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,15 @@
+
+ 4.0.0
+ $org.example
+ $untitled
+ $1.0-SNAPSHOT
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
diff --git a/untitled/src/main/resources/archetype-resources/src/main/java/App.java b/untitled/src/main/resources/archetype-resources/src/main/java/App.java
new file mode 100644
index 000000000..1fa6a9565
--- /dev/null
+++ b/untitled/src/main/resources/archetype-resources/src/main/java/App.java
@@ -0,0 +1,13 @@
+package $org.example;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/untitled/src/main/resources/archetype-resources/src/test/java/AppTest.java b/untitled/src/main/resources/archetype-resources/src/test/java/AppTest.java
new file mode 100644
index 000000000..65be417e7
--- /dev/null
+++ b/untitled/src/main/resources/archetype-resources/src/test/java/AppTest.java
@@ -0,0 +1,38 @@
+package $org.example;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}