From ed7bbde2041f32ef5e6c55c08d205ed83faa228f Mon Sep 17 00:00:00 2001 From: saurabhkumarkardam Date: Mon, 23 Sep 2024 07:51:31 +0000 Subject: [PATCH] feat(quorum): integrate aws secrets manager This PR will allow the use of the AWS service called Secrets Manager to store sensitive information, similar to how we use HashiCorp Vault for the same purpose. - A guide named "integrate-aws-secrets-manager-with-eks.md" has been introduced to help users securely connect their EKS cluster with Secrets Manager using OIDC. - The Quorum master README has been updated to guide users on how to deploy a network with AWS Secrets Manager. - A Python script has been added that contains the CRUD operation code for AWS Secrets Manager, injecting the script into the container via ConfigMap. - The Quorum Genesis and Node charts code have been updated to support Secrets Manager. fixes #2200 Signed-off-by: saurabhkumarkardam --- .../integrate-aws-secrets-manager-with-eks.md | 124 ++++++++ platforms/quorum/charts/README.md | 77 ++++- .../templates/genesis-job-init.yaml | 283 ++++++++++++------ .../templates/node-hooks-pre-install.yaml | 261 ++++++++++------ .../templates/node-statefulset.yaml | 12 +- .../noproxy-and-novault/genesis-sec.yaml | 6 +- .../values/noproxy-and-novault/genesis.yaml | 6 +- .../noproxy-and-novault/txnode-sec.yaml | 5 +- .../values/noproxy-and-novault/txnode.yaml | 5 +- .../values/noproxy-and-novault/validator.yaml | 5 +- .../scripts/aws-secret-manager-script.py | 103 +++++++ .../bevel-scripts/scripts/package-manager.sh | 2 +- .../bevel-scripts/templates/configmap.yaml | 14 + .../templates/serviceAccount.yaml | 7 +- 14 files changed, 704 insertions(+), 206 deletions(-) create mode 100644 docs/source/concepts/integrate-aws-secrets-manager-with-eks.md create mode 100644 platforms/shared/charts/bevel-scripts/scripts/aws-secret-manager-script.py diff --git a/docs/source/concepts/integrate-aws-secrets-manager-with-eks.md b/docs/source/concepts/integrate-aws-secrets-manager-with-eks.md new file mode 100644 index 00000000000..a7814010ce6 --- /dev/null +++ b/docs/source/concepts/integrate-aws-secrets-manager-with-eks.md @@ -0,0 +1,124 @@ +# `Integrating AWS Secrets Manager with an EKS Cluster` + +This guide walks you through the steps needed to create IAM policies, trust policies, and roles to securely integrate AWS Secrets Manager with your EKS cluster. By following these steps, you will enable your EKS workloads to securely access secrets stored in AWS Secrets Manager using OpenID Connect (OIDC) authentication. + +## Step 1: Create the `policy.json` File + +The `policy.json` file defines the permissions required to interact with AWS Secrets Manager. The IAM policy allows specific actions on all secrets within a region under your account. + +### `policy.json` content: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "secretsmanager:UntagResource", + "secretsmanager:DescribeSecret", + "secretsmanager:PutSecretValue", + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:CancelRotateSecret", + "secretsmanager:ListSecretVersionIds", + "secretsmanager:UpdateSecret", + "secretsmanager:GetResourcePolicy", + "secretsmanager:GetSecretValue", + "secretsmanager:StopReplicationToReplica", + "secretsmanager:ReplicateSecretToRegions", + "secretsmanager:RestoreSecret", + "secretsmanager:RotateSecret", + "secretsmanager:UpdateSecretVersionStage", + "secretsmanager:RemoveRegionsFromReplication", + "secretsmanager:TagResource" + ], + "Resource": "arn:aws:secretsmanager:::secret:*" + } + ] +} +``` + +### Notes: +- **Replace ``**: Your actual AWS account ID. +- **Replace ``**: AWS region where your secret is stored. + + +## Step 2: Create the Secrets Manager Policy + +Now that you have the `policy.json` file, create the IAM policy using the AWS CLI. + +### Command: + +```bash +aws iam create-policy --policy-name BevelSecretsManagerAccessPolicy --policy-document file:// +``` + +### Notes: +- **Replace ``**: Path to the `policy.json` file created in Step 1. +- This command creates an IAM policy named `BevelSecretsManagerAccessPolicy` with the specified permissions. + + +## Step 3: Create the `trust-policy.json` file + +Creates a trust policy to allow role assumption through OpenID Connect (OIDC). + +### `trust-policy.json` content: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + ":aud": "sts.amazonaws.com" + } + } + } + ] +} +``` + +### Notes: +- **Replace ``**: Use your AWS account ID. +- **Replace ``**: + - Insert your OpenID Connect provider URL without the `https://` prefix. + - For example, if your provider URL is `https://oidc.eks.eu-north-1.amazonaws.com/id/ABC1234567890`, you would use `oidc.eks.eu-north-1.amazonaws.com/id/ABC1234567890`. + +## Step 4: Create an IAM Role + +Once the trust policy is configured, create a IAM role that can assume this trust policy. + +### Command: + +```bash +aws iam create-role --role-name BevelEKSSecretsRole --assume-role-policy-document file:// +``` + +### Notes: +- **Replace ``**: Path to the `trust-policy.json` file created in Step 3. +- **Role Name**: The role created is named `BevelEKSSecretsRole`. + +This role now has the ability to assume the trust relationship defined by the OpenID Connect provider. + + +## Step 5: Attach the Secrets Manager Policy to the IAM Role + +After creating the role, attach the previously created `BevelSecretsManagerAccessPolicy` to the role `BevelEKSSecretsRole`, granting it the necessary permissions to manage AWS Secrets. + +### Command: + +```bash +aws iam attach-role-policy --role-name BevelEKSSecretsRole --policy-arn arn:aws:iam:::policy/BevelSecretsManagerAccessPolicy +``` + +### Notes: +- **Replace ``**: Ensure you substitute `` with your AWS account ID. +- The policy **`BevelSecretsManagerAccessPolicy`** is now attached to the role **`BevelEKSSecretsRole`**, allowing it to perform actions defined in the `policy.json` file. +--- diff --git a/platforms/quorum/charts/README.md b/platforms/quorum/charts/README.md index 47368595291..4a7c5ffb8cc 100644 --- a/platforms/quorum/charts/README.md +++ b/platforms/quorum/charts/README.md @@ -26,25 +26,27 @@ global: role: vault-role ``` -## Usage +
-### Pre-requisites +## `Usage` + +### Pre-requisites: - Kubernetes Cluster (either Managed cloud option like EKS or local like minikube) - Accessible and unsealed Hahsicorp Vault (if using Vault) - Configured Ambassador AES (if using Ambassador as proxy) - Update the dependencies - ``` - helm dependency update quorum-genesis - helm dependency update quorum-node - ``` + ``` + helm dependency update quorum-genesis + helm dependency update quorum-node + ``` +
## `Without Proxy and Vault` ### 1. Install Genesis Node ```bash -# Install the genesis node helm install genesis ./quorum-genesis --namespace supplychain-quo --create-namespace --values ./values/noproxy-and-novault/genesis.yaml ``` @@ -59,26 +61,68 @@ helm install validator-3 ./quorum-node --namespace supplychain-quo --values ./va ### 3. Deploy Member and Tessera Node Pair ```bash -# Deploy Quorum and Tessera node pair -helm install member-1 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/txnode.yaml +helm install member-0 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/txnode.yaml ``` ### Setting Up Another Member in a Different Namespace ```bash -# Get the genesis and static nodes from existing member and and place them in the directory 'besu-genesis/files' +# Get the genesis and static nodes from existing member and place them in the directory 'besu-genesis/files' cd ./quorum-genesis/files/ kubectl --namespace supplychain-quo get configmap quorum-peers -o jsonpath='{.data.static-nodes\.json}' > static-nodes.json kubectl --namespace supplychain-quo get configmap quorum-genesis -o jsonpath='{.data.genesis\.json}' > genesis.json # Install secondary genesis node -helm install genesis ./quorum-genesis --namespace carrier-quo --values ./values/noproxy-and-novault/genesis-sec.yaml +helm install genesis ./quorum-genesis --namespace carrier-quo --create-namespace --values ./values/noproxy-and-novault/genesis-sec.yaml # Install secondary member node -helm install member-2 ./quorum-node --namespace carrier-quo --values ./values/noproxy-and-novault/txnode-sec.yaml +helm install member-1 ./quorum-node --namespace carrier-quo --values ./values/noproxy-and-novault/txnode-sec.yaml +``` + +
+ +## `Without Proxy and AWS-Secret-Manager` + +### 1. Prerequisite: +- To securely integrate AWS Secrets Manager with an EKS Cluster, refer to the guide available [here.](../../../docs/source/concepts/integrate-aws-secrets-manager-with-eks.md) +- After completing all the steps mentioned in the guide, keep the IAM Role ARN handy. For example, it should look something like this: `arn:aws:iam:::role/BevelEKSSecretsRole` + +### 2. Install genesis node: +```bash +helm install genesis ./quorum-genesis --namespace supplychain-quo --create-namespace --values ./values/noproxy-and-novault/genesis.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerArn="",global.cluster.secretManagerRegion="" +``` + +### 3. Install validator nodes: +```bash +helm install validator-0 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/validator.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="" +helm install validator-1 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/validator.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="" +helm install validator-2 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/validator.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="" +helm install validator-3 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/validator.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="" +``` + +### 4. Install member node: +```bash +helm install member-0 ./quorum-node --namespace supplychain-quo --values ./values/noproxy-and-novault/txnode.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="",tessera.enabled=false ``` ---- +### 5. Setting Up Another Member in a Different Namespace + +```bash +# 5.1. Get the genesis and static nodes from existing member and place them in the directory 'besu-genesis/files' +cd ./quorum-genesis/files/ + +kubectl --namespace supplychain-quo get configmap quorum-peers -o jsonpath='{.data.static-nodes\.json}' > static-nodes.json + +kubectl --namespace supplychain-quo get configmap quorum-genesis -o jsonpath='{.data.genesis\.json}' > genesis.json + +# 5.2. Install secondary genesis node +helm install genesis ./quorum-genesis --namespace carrier-quo --create-namespace --values ./values/noproxy-and-novault/genesis-sec.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerArn="" + +# 5.3. Install secondary member node +helm install member-1 ./quorum-node --namespace carrier-quo --values ./values/noproxy-and-novault/txnode-sec.yaml --set global.cluster.cloudNativeServices=true,global.cluster.secretManagerRegion="",tessera.enabled=false +``` + +
## `With Ambassador Proxy and Vault` @@ -127,12 +171,14 @@ kubectl create namespace carrier-quo kubectl -n carrier-quo create secret generic roottoken --from-literal=token= # Install secondary genesis node -helm install genesis ./quorum-genesis --namespace carrier-quo --values ./values/proxy-and-vault/genesis-sec.yaml +helm install genesis ./quorum-genesis --namespace carrier-quo --create-namespace --values ./values/proxy-and-vault/genesis-sec.yaml # Install secondary member node helm install member-0 ./quorum-node --namespace carrier-quo --values ./values/proxy-and-vault/txnode-sec.yaml --set global.proxy.p2p=15016 ``` +
+ ## `API call` Once your services are deployed, they can be accessed using the domain name provided in your `global.proxy.externalUrlSuffix`. @@ -171,6 +217,8 @@ Once your services are deployed, they can be accessed using the domain name prov This confirms that your node is syncing as expected. +
+ ## `Managing IBFT Validators Deployment` To deploy the proposed validator chart for IBFT, you first need to set up the Quorum DLT network. Below are the steps you can follow: @@ -203,6 +251,7 @@ To deploy the proposed validator chart for IBFT, you first need to set up the Qu Replace `` with the appropriate host address. +
## `Clean-up` diff --git a/platforms/quorum/charts/quorum-genesis/templates/genesis-job-init.yaml b/platforms/quorum/charts/quorum-genesis/templates/genesis-job-init.yaml index 2e06ee22b2d..872bf7ea1d1 100644 --- a/platforms/quorum/charts/quorum-genesis/templates/genesis-job-init.yaml +++ b/platforms/quorum/charts/quorum-genesis/templates/genesis-job-init.yaml @@ -34,12 +34,30 @@ spec: securityContext: runAsUser: 0 imagePullPolicy: {{ .Values.image.pullPolicy }} -{{- if eq .Values.global.vault.type "hashicorp" }} volumeMounts: +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: package-manager + mountPath: /scripts/package-manager.sh + subPath: package-manager.sh + - name: aws-secret-manager-volume + mountPath: /etc/config + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} - name: scripts-volume mountPath: /scripts/bevel-vault.sh subPath: bevel-vault.sh + {{- end }} +{{- end }} env: +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: AWS_SECRECT_MANAGER_REGION + value: "{{ .Values.global.cluster.secretManagerRegion }}" + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} - name: VAULT_ADDR value: "{{ .Values.global.vault.address }}" - name: VAULT_SECRET_ENGINE @@ -52,6 +70,7 @@ spec: value: "{{ .Values.global.vault.role }}" - name: VAULT_TYPE value: "{{ .Values.global.vault.type }}" + {{- end }} {{- end }} command: - /bin/bash @@ -59,6 +78,103 @@ spec: args: - | + # reads all the required data from the file path + readSecretData() { + local fpath="$1" + + local node_address=$(cat "${fpath}/address") + local node_key=$(cat "${fpath}/nodekey") + local node_key_pub=$(cat "${fpath}/nodekey.pub") + local account_private_key=$(cat "${fpath}/accountPrivateKey") + local account_password=$(cat "${fpath}/accountPassword") + local account_keystore=$(cat "${fpath}/accountKeystore") + local account_keystore_base64=$(cat "${fpath}/accountKeystore" | base64 -w 0) + local account_address=$(cat "${fpath}/accountAddress") + + echo "${node_address}" "${node_key}" "${node_key_pub}" "${account_private_key}" "${account_password}" "${account_keystore}" "${account_keystore_base64}" "${account_address}" + } + + # constructs the JSON payload using the extracted secret values + createJsonPayload() { + local node_address="$1" + local node_key="$2" + local node_key_pub="$3" + local account_private_key="$4" + local account_password="$5" + local account_keystore_base64="$6" + local account_address="$7" + + echo " + { + \"data\": + { + \"nodeAddress\": \"${node_address}\", + \"nodeKey\": \"${node_key}\", + \"nodeKeyPub\": \"${node_key_pub}\", + \"accountPrivateKey\": \"${account_private_key}\", + \"accountPassword\": \"${account_password}\", + \"accountKeystore_base64\": \"${account_keystore_base64}\", + \"accountAddress\": \"${account_address}\" + } + }" > nodePayload.json + } + + # checks if a secret exists in AWS Secret Manager, and either creates or updates it + manageAwsSecret() { + local key="$1" + local payload_file="$2" + + # Check if the secret exists in AWS Secret Manager + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" get_secret "${key}" > /tmp/response.json + # If the secret does not exist, create it, otherwise update it + if grep -q "ResourceNotFoundException" /tmp/response.json; then + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" create_secret "${key}" "${payload_file}" + else + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" update_secret "${key}" "${payload_file}" + fi + } + + # Function to create a Kubernetes secret + createKubernetesSecret() { + local key="$1" + local node_address="$2" + local node_key="$3" + local node_key_pub="$4" + local account_private_key="$5" + local account_password="$6" + local account_keystore="$7" + local account_address="$8" + + # Create the Kubernetes secret + kubectl create secret generic ${key} --namespace "{{ .Release.Namespace }}" \ + --from-literal=address="${node_address}" \ + --from-literal=nodekey="${node_key}" \ + --from-literal=nodekey.pub="${node_key_pub}" \ + --from-literal=accountPrivateKey="${account_private_key}" \ + --from-literal=accountPassword="${account_password}" \ + --from-literal=accountKeystore="${account_keystore}" \ + --from-literal=accountAddress="${account_address}" \ + --dry-run=client -o yaml | kubectl apply -f - + } + + + readGenesisData() { + local fpath="$1" + local genesis_base64=$(cat "${fpath}/genesis.json" | base64 -w 0) + echo "${genesis_base64}" + } + + createGenesisJsonPayload() { + local genesis_base64="$1" + echo " + { + \"data\": + { + \"genesis_base64\": \"${genesis_base64}\" + } + }" > encryptedGenesisPayload.json + } + # Check if the vault type is HashiCorp {{- if eq .Values.global.vault.type "hashicorp" }} # Source the script containing vault-related functions @@ -78,53 +194,23 @@ spec: if [ "$SECRETS_AVAILABLE" == "yes" ] then # Extract secrets from JSON response - local accountAddress=$(echo ${VAULT_SECRET} | jq -r '.["accountAddress"]') - local accountKeystore=$(echo ${VAULT_SECRET} | jq -r '.["accountKeystore_base64"]' | base64 -d) - local accountPassword=$(echo ${VAULT_SECRET} | jq -r '.["accountPassword"]') - local accountPrivateKey=$(echo ${VAULT_SECRET} | jq -r '.["accountPrivateKey"]') - local address=$(echo ${VAULT_SECRET} | jq -r '.["nodeAddress"]') - local nodeKey=$(echo ${VAULT_SECRET} | jq -r '.["nodeKey"]') - local nodekey_pub=$(echo ${VAULT_SECRET} | jq -r '.["nodeKeyPub"]') + local node_address=$(echo ${VAULT_SECRET} | jq -r '.["nodeAddress"]') + local node_key=$(echo ${VAULT_SECRET} | jq -r '.["nodeKey"]') + local node_key_pub=$(echo ${VAULT_SECRET} | jq -r '.["nodeKeyPub"]') + local account_private_key=$(echo ${VAULT_SECRET} | jq -r '.["accountPrivateKey"]') + local account_password=$(echo ${VAULT_SECRET} | jq -r '.["accountPassword"]') + local account_Keystore=$(echo ${VAULT_SECRET} | jq -r '.["accountKeystore_base64"]' | base64 -d) + local account_Address=$(echo ${VAULT_SECRET} | jq -r '.["accountAddress"]') - # Check if Kubernetes secret exists, if not, create one - if ! kubectl get secret quorum-node-validator-${i}-keys --namespace {{ .Release.Namespace }} &> /dev/null; then - kubectl create secret generic quorum-node-validator-${i}-keys --namespace {{ .Release.Namespace }} \ - --from-literal=accountAddress=${accountAddress} \ - --from-literal=accountKeystore=${accountKeystore} \ - --from-literal=accountPassword=${accountPassword} \ - --from-literal=accountPrivateKey=${accountPrivateKey} \ - --from-literal=address=${address} \ - --from-literal=nodekey=${nodeKey} \ - --from-literal=nodekey.pub=${nodekey_pub} - fi + # Create Kubernetes Secret + createKubernetesSecret "$key" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" else - # Read data from files if secrets are not available in the vault - local node_address=$(cat "${fpath}/address") - local node_key=$(cat "${fpath}/nodekey") - local node_key_pub=$(cat "${fpath}/nodekey.pub") - local account_private_key=$(cat "${fpath}/accountPrivateKey") - local account_password=$(cat "${fpath}/accountPassword") - local account_keystore_base64=$(cat "${fpath}/accountKeystore" | base64 -w 0) - local account_address=$(cat "${fpath}/accountAddress") - - # Construct JSON payload - echo " - { - \"data\": - { - \"nodeAddress\": \"${node_address}\", - \"nodeKey\": \"${node_key}\", - \"nodeKeyPub\": \"${node_key_pub}\", - \"accountPrivateKey\": \"${account_private_key}\", - \"accountPassword\": \"${account_password}\", - \"accountKeystore_base64\": \"${account_keystore_base64}\", - \"accountAddress\": \"${account_address}\" - } - }" > nodePayload.json - + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$fpath") + # Create JSON payload + createJsonPayload "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore_base64" "$account_address" # Push data to vault vaultBevelFunc 'write' "${VAULT_SECRET_ENGINE}/${VAULT_SECRET_PREFIX}/${key}" 'nodePayload.json' - rm nodePayload.json fi } @@ -147,28 +233,52 @@ spec: else # Read genesis data from files if secrets are not available in the vault local genesis_base64=$(cat "${fpath}/genesis.json" | base64 -w 0) - - # Construct JSON payload - echo " - { - \"data\": - { - \"genesis_base64\": \"${genesis_base64}\" - } - }" > genesisPayload.json - + # Create genesis JSON payload + createGenesisJsonPayload "$genesis_base64" # Push data to vault - vaultBevelFunc 'write' "${VAULT_SECRET_ENGINE}/${VAULT_SECRET_PREFIX}/${key}" 'genesisPayload.json' - - rm genesisPayload.json + vaultBevelFunc 'write' "${VAULT_SECRET_ENGINE}/${VAULT_SECRET_PREFIX}/${key}" 'encryptedGenesisPayload.json' fi } +{{- else if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + . /scripts/package-manager.sh + # Define the packages to install + packages_to_install="python3-pip python3-boto3" + install_packages "$packages_to_install" + + safeWriteSecret() { + local key="$1" + local fpath="$2" + + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$fpath") + # Create JSON payload + createJsonPayload "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore_base64" "$account_address" + # Manage AWS Secret + manageAwsSecret "${key}" "nodePayload.json" + # Create Kubernetes Secret + createKubernetesSecret "$key" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" + } + + # Function to safely write genesis + safeWriteGenesis() { + local key="$1" + local fpath="$2" + + # Read genesis data + local genesis_data=$(readGenesisData "$fpath") + # Create genesis JSON payload + createGenesisJsonPayload "$genesis_data" + # Manage AWS Secret for Genesis + manageAwsSecret "${key}" "encryptedGenesisPayload.json" + } + {{- end }} {{- else }} safeWriteSecret() { # Placeholder: # - Implement code to fetch the keys if using any cloud-native service or platform different from HashiCorp to store the keys # - After fetching the keys, create Kubernetes secrets from them - # - For guidance, refer to the code written for HashiCorp Vault for the same purpose + # - For guidance, refer to the code written for HashiCorp Vault and AWS Secret Manager for the same purpose return 0 } {{- end }} @@ -176,7 +286,7 @@ spec: {{- if .Values.settings.secondaryGenesis }} echo "Secondary Genesis, configmaps are created from local files." {{- else }} - # Use quorum-genesis-tool to generate genesis, keys and other required files + # Use quorum-genesis-tool to generate genesis, keys and other required files FOLDER_PATH=$(quorum-genesis-tool --consensus {{ .Values.rawGenesisConfig.genesis.config.algorithm.consensus }} \ {{ if .Values.rawGenesisConfig.blockchain.nodes.generate }} --validators {{ .Values.rawGenesisConfig.blockchain.nodes.count }} {{ else }} --validators 0 {{ end }} \ --members 0 --bootnodes 0 --chainID {{ .Values.rawGenesisConfig.genesis.config.chainId }} --blockperiod {{ .Values.rawGenesisConfig.genesis.config.algorithm.blockperiodseconds }} \ @@ -186,53 +296,52 @@ spec: {{ if .Values.rawGenesisConfig.blockchain.accountPassword }} --accountPassword {{ .Values.rawGenesisConfig.blockchain.accountPassword }} {{ end }} \ --quickstartDevAccounts {{ .Values.rawGenesisConfig.genesis.includeQuickStartAccounts }} \ --outputPath /generated-config | tail -1 | sed -e "s/^Artifacts in folder: //") - - # Check if quorum-genesis configmap exists, if not, create one - if ! kubectl get configmap "quorum-genesis" --namespace {{ .Release.Namespace }} &> /dev/null; then - kubectl create configmap "quorum-genesis" --namespace {{ .Release.Namespace }} --from-file=genesis.json="$FOLDER_PATH/goQuorum/genesis.json" - fi - + + # Check if the quorum-genesis ConfigMap exists, if not, create or update it + kubectl create configmap "quorum-genesis" --namespace {{ .Release.Namespace }} --from-file=genesis.json="$FOLDER_PATH/goQuorum/genesis.json" --dry-run=client -o yaml | kubectl apply -f - + # Count the number of validators based on their directories validator_count=$(ls -d $FOLDER_PATH/validator* | wc -l) # Iterate through the validators using a for loop for ((i = 0; i < validator_count; i++)); do current_validator_dir="$FOLDER_PATH/validator${i}" + secret_name="quorum-node-validator-${i}-{{ .Release.Namespace }}-keys" {{- if and (ne .Values.global.cluster.provider "minikube") (.Values.global.cluster.cloudNativeServices) }} - # Safely write keys for cloud-native services - safeWriteSecret quorum-node-validator-${i}-nodekey ${current_validator_dir}/nodekey - safeWriteSecret quorum-node-validator-${i}-nodekeypub ${current_validator_dir}/nodekey.pub - safeWriteSecret quorum-node-validator-${i}-enode ${current_validator_dir}/nodekey.pub - safeWriteSecret quorum-node-validator-${i}-address ${current_validator_dir}/address - kubectl create configmap --namespace {{ .Release.Namespace }} "quorum-node-validator-${i}-address" --from-file=address=${current_validator_dir}/address - safeWriteSecret quorum-node-validator-${i}-accountPrivateKey ${current_validator_dir}/accountPrivateKey - safeWriteSecret quorum-node-validator-${i}-accountPassword ${current_validator_dir}/accountPassword - safeWriteSecret quorum-node-validator-${i}-accountKeystore ${current_validator_dir}/accountKeystore - safeWriteSecret quorum-node-validator-${i}-accountAddress ${current_validator_dir}/accountAddress + # Implement logic to safely write keys for cloud-native services + # If cloud provider is aws, secret-manager will be used to store keys securely + safeWriteSecret "$secret_name" "${current_validator_dir}" {{- else }} # Safely write keys and genesis to the Hashicorp Vault - safeWriteSecret "quorum-node-validator-${i}-keys" "${current_validator_dir}" - safeWriteGenesis "quorum-genesis" "$FOLDER_PATH/goQuorum" + safeWriteSecret "$secret_name" "${current_validator_dir}" {{- end }} # Check if Kubernetes secret exists, if not, create one - if ! kubectl get secret quorum-node-validator-${i}-keys --namespace {{ .Release.Namespace }} &> /dev/null; then - kubectl create secret generic quorum-node-validator-${i}-keys --namespace {{ .Release.Namespace }} \ - --from-literal=accountAddress=$(cat "${current_validator_dir}/accountAddress") \ - --from-literal=accountKeystore=$(cat "${current_validator_dir}/accountKeystore") \ - --from-literal=accountPassword=$(cat "${current_validator_dir}/accountPassword") \ - --from-literal=accountPrivateKey=$(cat "${current_validator_dir}/accountPrivateKey") \ - --from-literal=address=$(cat "${current_validator_dir}/address") \ - --from-literal=nodekey=$(cat "${current_validator_dir}/nodekey") \ - --from-literal=nodekey.pub=$(cat "${current_validator_dir}/nodekey.pub") + if ! kubectl get secret "$secret_name" --namespace {{ .Release.Namespace }} &> /dev/null; then + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$current_validator_dir") + # Create Kubernetes Secret + createKubernetesSecret "$secret_name" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" fi done + safeWriteGenesis "quorum-genesis" "$FOLDER_PATH/goQuorum" {{- end }} echo "Completed." -{{- if eq .Values.global.vault.type "hashicorp" }} volumes: +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: package-manager + configMap: + name: package-manager + - name: aws-secret-manager-volume + configMap: + name: aws-secret-manager-script + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} - name: scripts-volume configMap: name: bevel-vault-script defaultMode: 0777 + {{- end }} {{- end }} diff --git a/platforms/quorum/charts/quorum-node/templates/node-hooks-pre-install.yaml b/platforms/quorum/charts/quorum-node/templates/node-hooks-pre-install.yaml index 52b48025ecf..ed8eb7e6d11 100644 --- a/platforms/quorum/charts/quorum-node/templates/node-hooks-pre-install.yaml +++ b/platforms/quorum/charts/quorum-node/templates/node-hooks-pre-install.yaml @@ -31,24 +31,43 @@ spec: image: {{ .Values.image.hooks.repository }}:{{ .Values.image.hooks.tag }} securityContext: runAsUser: 0 -{{- if eq .Values.global.vault.type "hashicorp" }} volumeMounts: +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: package-manager + mountPath: /scripts/package-manager.sh + subPath: package-manager.sh + - name: aws-secret-manager-volume + mountPath: /etc/config + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} - name: scripts-volume mountPath: /scripts/bevel-vault.sh subPath: bevel-vault.sh + {{- end }} +{{- end }} env: - - name: VAULT_ADDR - value: "{{ .Values.global.vault.address }}" - - name: VAULT_SECRET_ENGINE - value: "{{ .Values.global.vault.secretEngine }}" - - name: VAULT_SECRET_PREFIX - value: "{{ .Values.global.vault.secretPrefix }}" - - name: KUBERNETES_AUTH_PATH - value: "{{ .Values.global.vault.authPath }}" - - name: VAULT_APP_ROLE - value: "{{ .Values.global.vault.role }}" - - name: VAULT_TYPE - value: "{{ .Values.global.vault.type }}" +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: AWS_SECRECT_MANAGER_REGION + value: "{{ .Values.global.cluster.secretManagerRegion }}" + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} + - name: VAULT_ADDR + value: "{{ .Values.global.vault.address }}" + - name: VAULT_SECRET_ENGINE + value: "{{ .Values.global.vault.secretEngine }}" + - name: VAULT_SECRET_PREFIX + value: "{{ .Values.global.vault.secretPrefix }}" + - name: KUBERNETES_AUTH_PATH + value: "{{ .Values.global.vault.authPath }}" + - name: VAULT_APP_ROLE + value: "{{ .Values.global.vault.role }}" + - name: VAULT_TYPE + value: "{{ .Values.global.vault.type }}" + {{- end }} {{- end }} command: - /bin/bash @@ -56,6 +75,85 @@ spec: args: - | + # reads all the required data from the file path + readSecretData() { + local fpath="$1" + + local node_address=$(cat "${fpath}/address") + local node_key=$(cat "${fpath}/nodekey") + local node_key_pub=$(cat "${fpath}/nodekey.pub") + local account_private_key=$(cat "${fpath}/accountPrivateKey") + local account_password=$(cat "${fpath}/accountPassword") + local account_keystore=$(cat "${fpath}/accountKeystore") + local account_keystore_base64=$(cat "${fpath}/accountKeystore" | base64 -w 0) + local account_address=$(cat "${fpath}/accountAddress") + + echo "${node_address}" "${node_key}" "${node_key_pub}" "${account_private_key}" "${account_password}" "${account_keystore}" "${account_keystore_base64}" "${account_address}" + } + + # constructs the JSON payload using the extracted secret values + createJsonPayload() { + local node_address="$1" + local node_key="$2" + local node_key_pub="$3" + local account_private_key="$4" + local account_password="$5" + local account_keystore_base64="$6" + local account_address="$7" + + echo " + { + \"data\": + { + \"nodeAddress\": \"${node_address}\", + \"nodeKey\": \"${node_key}\", + \"nodeKeyPub\": \"${node_key_pub}\", + \"accountPrivateKey\": \"${account_private_key}\", + \"accountPassword\": \"${account_password}\", + \"accountKeystore_base64\": \"${account_keystore_base64}\", + \"accountAddress\": \"${account_address}\" + } + }" > nodePayload.json + } + + # checks if a secret exists in AWS Secret Manager, and either creates or updates it + manageAwsSecret() { + local key="$1" + local payload_file="$2" + + # Check if the secret exists in AWS Secret Manager + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" get_secret "${key}" > /tmp/response.json + # If the secret does not exist, create it, otherwise update it + if grep -q "ResourceNotFoundException" /tmp/response.json; then + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" create_secret "${key}" "${payload_file}" + else + python3 /etc/config/aws-secret-manager-script.py "${AWS_SECRECT_MANAGER_REGION}" update_secret "${key}" "${payload_file}" + fi + } + + # Function to create a Kubernetes secret + createKubernetesSecret() { + local key="$1" + local node_address="$2" + local node_key="$3" + local node_key_pub="$4" + local account_private_key="$5" + local account_password="$6" + local account_keystore="$7" + local account_address="$8" + + # Create the Kubernetes secret + kubectl create secret generic ${key} --namespace "{{ .Release.Namespace }}" \ + --from-literal=address="${node_address}" \ + --from-literal=nodekey="${node_key}" \ + --from-literal=nodekey.pub="${node_key_pub}" \ + --from-literal=accountPrivateKey="${account_private_key}" \ + --from-literal=accountPassword="${account_password}" \ + --from-literal=accountKeystore="${account_keystore}" \ + --from-literal=accountAddress="${account_address}" \ + --dry-run=client -o yaml | kubectl apply -f - + } + # Check if the vault type is HashiCorp {{- if eq .Values.global.vault.type "hashicorp" }} # Source the script containing vault-related functions @@ -84,62 +182,56 @@ spec: local nodeKey=$(echo ${VAULT_SECRET} | jq -r '.["nodeKey"]') nodekey_pub=$(echo ${VAULT_SECRET} | jq -r '.["nodeKeyPub"]') - # Check if Kubernetes secret exists, if not, create one - if ! kubectl get secret {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} &> /dev/null; then - # Create Kubernetes secret from vault secrets - kubectl create secret generic {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} \ - --from-literal=accountAddress=${accountAddress} \ - --from-literal=accountKeystore=${accountKeystore} \ - --from-literal=accountPassword=${accountPassword} \ - --from-literal=accountPrivateKey=${accountPrivateKey} \ - --from-literal=address=${address} \ - --from-literal=nodekey=${nodeKey} \ - --from-literal=nodekey.pub=${nodekey_pub} - fi + # Create Kubernetes Secret + createKubernetesSecret "$key" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" else - # Read data from files if secrets are not available in the vault - local accountAddress=$(cat "${fpath}/accountAddress") - local accountKeystore_base64=$(cat "${fpath}/accountKeystore" | base64 -w 0) - local accountPassword=$(cat "${fpath}/accountPassword") - local accountPrivateKey=$(cat "${fpath}/accountPrivateKey") - local address=$(cat "${fpath}/address") - local nodekey=$(cat "${fpath}/nodekey") - local nodekey_pub=$(cat "${fpath}/nodekey.pub") - - # Construct JSON payload - echo " - { - \"data\": - { - \"accountAddress\": \"${accountAddress}\", - \"accountKeystore_base64\": \"${accountKeystore_base64}\", - \"accountPassword\": \"${accountPassword}\", - \"accountPrivateKey\": \"${accountPrivateKey}\", - \"nodeAddress\": \"${address}\", - \"nodeKey\": \"${nodekey}\", - \"nodeKeyPub\": \"${nodekey_pub}\" - } - }" > nodePayload.json - + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$fpath") + # Create JSON payload + createJsonPayload "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore_base64" "$account_address" # Push data to vault vaultBevelFunc 'write' "${VAULT_SECRET_ENGINE}/${VAULT_SECRET_PREFIX}/${key}" 'nodePayload.json' - + # Remove payload JSON rm nodePayload.json fi } +{{- else if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + . /scripts/package-manager.sh + + # Define the packages to install + packages_to_install="python3-pip python3-boto3" + install_packages "$packages_to_install" + + safeWriteSecret() { + local key="$1" + local fpath="$2" + + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$fpath") + # Create JSON payload + createJsonPayload "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore_base64" "$account_address" + # Manage AWS Secret + manageAwsSecret "${key}" "nodePayload.json" + # Create Kubernetes Secret + createKubernetesSecret "$key" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" + # Remove payload JSON + rm nodePayload.json + } + {{- end }} {{- else }} safeWriteSecret() { # Placeholder: - # - Implement code to fetch the keys if using any cloud-native service or platform different from HashiCorp to store the keys + # - Implement code to fetch the keys if using any cloud-native service or platform different from HashiCorp or AWS-Secret-Manager to store the keys # - After fetching the keys, create Kubernetes secrets from them - # - For guidance, refer to the code written for HashiCorp Vault for the same purpose + # - For guidance, refer to the code written for HashiCorp Vault and AWS Secret Manager for the same purpose return 0 } {{- end }} - + secret_name="{{ template "quorum-node.fullname" . }}-{{ .Release.Namespace }}-keys" # Check if the secret exists in Kubernetes - if ! kubectl get secret {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} &> /dev/null; then - echo "Secret does not exist. Creating secret." + if ! kubectl get secret "$secret_name" --namespace {{ .Release.Namespace }} &> /dev/null; then + echo "Secret $secret_name does not exist. Creating secret." # Use quorum-genesis-tool to generate genesis, keys and other required files FOLDER_PATH=$(quorum-genesis-tool --validators 0 --members 1 --bootnodes 0 \ @@ -148,39 +240,25 @@ spec: dir="$FOLDER_PATH/member0" {{- if and (ne .Values.global.cluster.provider "minikube") (.Values.global.cluster.cloudNativeServices) }} - # Safely write keys for cloud-native services - echo "Creating keys in vault for {{ template "quorum-node.fullname" . }} ..." - safeWriteSecret {{ template "quorum-node.fullname" . }}-nodekey ${dir}/nodekey - safeWriteSecret {{ template "quorum-node.fullname" . }}-nodekeypub ${dir}/nodekey.pub - safeWriteSecret {{ template "quorum-node.fullname" . }}-enode ${dir}/nodekey.pub - safeWriteSecret {{ template "quorum-node.fullname" . }}-address ${dir}/address - safeWriteSecret {{ template "quorum-node.fullname" . }}-accountPrivateKey ${dir}/accountPrivateKey - safeWriteSecret {{ template "quorum-node.fullname" . }}-accountPassword ${dir}/accountPassword - safeWriteSecret {{ template "quorum-node.fullname" . }}-accountKeystore ${dir}/accountKeystore - safeWriteSecret {{ template "quorum-node.fullname" . }}-accountAddress ${dir}/accountAddress + # Implement logic to safely write keys for cloud-native services + # If cloud provider is aws, secret-manager will be used to store keys securely + safeWriteSecret "$secret_name" "${dir}" {{- else }} # Safely write keys to the Hashicorp Vault - safeWriteSecret "{{ template "quorum-node.fullname" . }}-keys" "${dir}" + safeWriteSecret "$secret_name" "${dir}" {{- end }} - # Check if Kubernetes secret exists, if not, create one - if ! kubectl get secret {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} &> /dev/null; then - kubectl create secret generic {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} \ - --from-literal=accountAddress=$(cat "${dir}/accountAddress") \ - --from-literal=accountKeystore=$(cat "${dir}/accountKeystore") \ - --from-literal=accountPassword=$(cat "${dir}/accountPassword") \ - --from-literal=accountPrivateKey=$(cat "${dir}/accountPrivateKey") \ - --from-literal=address=$(cat "${dir}/address") \ - --from-literal=nodekey=$(cat "${dir}/nodekey") \ - --from-literal=nodekey.pub=$(cat "${dir}/nodekey.pub") - - nodekey_pub=$(cat "${dir}/nodekey.pub") + if ! kubectl get secret "$secret_name" --namespace {{ .Release.Namespace }} &> /dev/null; then + # Read secret data + read -r node_address node_key node_key_pub account_private_key account_password account_keystore account_keystore_base64 account_address <<< $(readSecretData "$current_validator_dir") + # Create Kubernetes Secret + createKubernetesSecret "$secret_name" "$node_address" "$node_key" "$node_key_pub" "$account_private_key" "$account_password" "$account_keystore" "$account_address" fi else - echo "Secret exists. Extract modekey.pub key" - nodekey_pub=$(kubectl get secret {{ template "quorum-node.fullname" . }}-keys --namespace {{ .Release.Namespace }} -o json | jq -r '.data["nodekey.pub"]' | base64 -d) + echo "Secret $secret_name exists. Extract modekey.pub key" + node_key_pub=$(kubectl get secret "$secret_name" --namespace {{ .Release.Namespace }} -o json | jq -r '.data["nodekey.pub"]' | base64 -d) fi - + quorum_peers_configmap="quorum-peers" # Check if the ConfigMap exists if kubectl get configmap "$quorum_peers_configmap" --namespace {{ .Release.Namespace }} &> /dev/null; then @@ -188,9 +266,9 @@ spec: kubectl get configmap "$quorum_peers_configmap" --namespace {{ .Release.Namespace }} -o json | jq -r '.data["static-nodes.json"]' > ./static-nodes.json echo "Content of the ConfigMap has been saved to static-nodes.json" existingStaticNodes=$(cat ./static-nodes.json) - - newStaticNode="enode://$nodekey_pub@{{ include "quorum-node.enodeURL" . }}?discport=0" - + + newStaticNode="enode://$node_key_pub@{{ include "quorum-node.enodeURL" . }}?discport=0" + # Check if newStaticNode already exists in existingStaticNodes if ! echo "$existingStaticNodes" | jq 'contains(["'"$newStaticNode"'"])' | grep -q true; then existingStaticNodes=$(jq ". + [\"$newStaticNode\"]" <<< "$existingStaticNodes") @@ -204,7 +282,7 @@ spec: # Creating static-nodes.json file echo "[" > "./static-nodes.json" - echo "\"enode://$nodekey_pub@{{ include "quorum-node.enodeURL" . }}?discport=0\"" >> "./static-nodes.json" + echo "\"enode://$node_key_pub@{{ include "quorum-node.enodeURL" . }}?discport=0\"" >> "./static-nodes.json" echo "]" >> "./static-nodes.json" # Create ConfigMap @@ -212,10 +290,21 @@ spec: fi echo "COMPLETED PRE-HOOK" -{{- if eq .Values.global.vault.type "hashicorp" }} volumes: +{{- if .Values.global.cluster.cloudNativeServices }} + {{- if eq .Values.global.cluster.provider "aws" }} + - name: package-manager + configMap: + name: package-manager + - name: aws-secret-manager-volume + configMap: + name: aws-secret-manager-script + {{- end }} +{{- else }} + {{- if eq .Values.global.vault.type "hashicorp" }} - name: scripts-volume configMap: name: bevel-vault-script defaultMode: 0777 + {{- end }} {{- end }} diff --git a/platforms/quorum/charts/quorum-node/templates/node-statefulset.yaml b/platforms/quorum/charts/quorum-node/templates/node-statefulset.yaml index 5277e92b22d..39f935c6087 100644 --- a/platforms/quorum/charts/quorum-node/templates/node-statefulset.yaml +++ b/platforms/quorum/charts/quorum-node/templates/node-statefulset.yaml @@ -147,20 +147,20 @@ spec: # Create the necessary directory structure mkdir -p {{ .Release.Name }}/data/keystore - + # Move files to their respective locations cp staticNode/static-nodes.json {{ .Release.Name }}/data/ cp genesis/genesis.json {{ .Release.Name }}/data/ - cp nodeSecrets/nodekey* {{ .Release.Name }}/nodeSecrets/address {{ .Release.Name }}/data/ + cp nodeSecrets/nodekey* nodeSecrets/address {{ .Release.Name }}/data/ cp nodeSecrets/account* {{ .Release.Name }}/data/keystore/ cd {{ .Release.Name }}/ - + # Initialize the node geth --datadir data init data/genesis.json - + # Extract the address from the account keystore file export ADDRESS=$(grep -o '"address": *"[^"]*"' ./data/keystore/accountKeystore | grep -o '"[^"]*"$' | sed 's/"//g') - + # Start the node geth \ --datadir {{ .Values.node.quorum.dataPath }} \ @@ -196,4 +196,4 @@ spec: name: quorum-genesis - name: node-keys secret: - secretName: {{ template "quorum-node.fullname" . }}-keys + secretName: {{ template "quorum-node.fullname" . }}-{{ .Release.Namespace }}-keys diff --git a/platforms/quorum/charts/values/noproxy-and-novault/genesis-sec.yaml b/platforms/quorum/charts/values/noproxy-and-novault/genesis-sec.yaml index 2101dcb16be..055c659cac5 100644 --- a/platforms/quorum/charts/values/noproxy-and-novault/genesis-sec.yaml +++ b/platforms/quorum/charts/values/noproxy-and-novault/genesis-sec.yaml @@ -2,11 +2,11 @@ global: serviceAccountName: vault-auth cluster: + cloudNativeServices: false # true | false provider: aws - cloudNativeServices: false + secretManagerArn: vault: - type: kubernetes - + type: kubernetes # kubernetes settings: removeGenesisOnDelete: true secondaryGenesis: true diff --git a/platforms/quorum/charts/values/noproxy-and-novault/genesis.yaml b/platforms/quorum/charts/values/noproxy-and-novault/genesis.yaml index a8a83626b31..f75e833039c 100644 --- a/platforms/quorum/charts/values/noproxy-and-novault/genesis.yaml +++ b/platforms/quorum/charts/values/noproxy-and-novault/genesis.yaml @@ -2,9 +2,11 @@ global: serviceAccountName: vault-auth cluster: + cloudNativeServices: false # true | false provider: aws - cloudNativeServices: false + secretManagerArn: + secretManagerRegion: vault: - type: kubernetes + type: kubernetes # kubernetes settings: removeGenesisOnDelete: true diff --git a/platforms/quorum/charts/values/noproxy-and-novault/txnode-sec.yaml b/platforms/quorum/charts/values/noproxy-and-novault/txnode-sec.yaml index 3e1e4ac8527..a5c673927be 100644 --- a/platforms/quorum/charts/values/noproxy-and-novault/txnode-sec.yaml +++ b/platforms/quorum/charts/values/noproxy-and-novault/txnode-sec.yaml @@ -3,10 +3,11 @@ global: serviceAccountName: vault-auth cluster: + cloudNativeServices: false # true | false provider: aws - cloudNativeServices: false + secretManagerRegion: vault: - type: kubernetes + type: kubernetes # kubernetes proxy: provider: none externalUrlSuffix: svc.cluster.local diff --git a/platforms/quorum/charts/values/noproxy-and-novault/txnode.yaml b/platforms/quorum/charts/values/noproxy-and-novault/txnode.yaml index fdde1d2b51b..49c95e8b695 100644 --- a/platforms/quorum/charts/values/noproxy-and-novault/txnode.yaml +++ b/platforms/quorum/charts/values/noproxy-and-novault/txnode.yaml @@ -4,10 +4,11 @@ global: serviceAccountName: vault-auth cluster: + cloudNativeServices: false # true | false provider: aws - cloudNativeServices: false + secretManagerRegion: vault: - type: kubernetes + type: kubernetes # kubernetes proxy: provider: none externalUrlSuffix: svc.cluster.local diff --git a/platforms/quorum/charts/values/noproxy-and-novault/validator.yaml b/platforms/quorum/charts/values/noproxy-and-novault/validator.yaml index bcaba85f48f..e1517a01e7f 100644 --- a/platforms/quorum/charts/values/noproxy-and-novault/validator.yaml +++ b/platforms/quorum/charts/values/noproxy-and-novault/validator.yaml @@ -4,10 +4,11 @@ global: serviceAccountName: vault-auth cluster: + cloudNativeServices: false # true | false provider: aws - cloudNativeServices: false # set to true to use Cloud Native Services (SecretsManager and IAM for AWS; KeyVault & Managed Identities for Azure) + secretManagerRegion: vault: - type: kubernetes + type: kubernetes # kubernetes proxy: provider: none diff --git a/platforms/shared/charts/bevel-scripts/scripts/aws-secret-manager-script.py b/platforms/shared/charts/bevel-scripts/scripts/aws-secret-manager-script.py new file mode 100644 index 00000000000..61122646336 --- /dev/null +++ b/platforms/shared/charts/bevel-scripts/scripts/aws-secret-manager-script.py @@ -0,0 +1,103 @@ +import boto3 +from botocore.exceptions import ClientError +import argparse +import json + +# Initialize the Secrets Manager client as a global variable to be used across all CRUD operations +client = None + +# Create a new secret +def create_secret(secret_name, secret_value): + try: + response = client.create_secret( + Name=secret_name, + Description="My application secret", + SecretString=secret_value + ) + print(f"Secret {secret_name} created successfully!") + print(f"response {response}") + return response + except ClientError as e: + print(f"Error creating secret: {e}") + return None + +# Retrieve an existing secret +def get_secret(secret_name): + try: + response = client.get_secret_value(SecretId=secret_name) + secret = response['SecretString'] + print(f"Retrieved secret: {secret}") + return secret + except ClientError as e: + print(f"Error retrieving secret: {e}") + return None + +# Update an existing secret +def update_secret(secret_name, new_secret_value): + try: + response = client.update_secret( + SecretId=secret_name, + SecretString=new_secret_value + ) + print(f"Secret {secret_name} updated successfully!") + print(f"response {response}") + return response + except ClientError as e: + print(f"Error updating secret: {e}") + return None + +# Delete an existing secret +def delete_secret(secret_name): + try: + response = client.delete_secret( + SecretId=secret_name, + ForceDeleteWithoutRecovery=True + ) + print(f"Secret {secret_name} scheduled for deletion!") + return response + except ClientError as e: + print(f"Error deleting secret: {e}") + return None + +# Main entry point to handle different secret management operations +if __name__ == "__main__": + # Set up command-line argument parsing + parser = argparse.ArgumentParser(description='Manage AWS Secrets') + # Define required arguments: region_name, operation (create/get/update/delete) and secret_name + parser.add_argument('region_name', help='AWS region name (e.g., us-east-1, eu-central-1)') + parser.add_argument('operation', choices=['create_secret', 'get_secret', 'update_secret', 'delete_secret'], help='Operation to perform') + parser.add_argument('secret_name', help='Name of the secret') + # Optional argument for secret value, required only for create and update operations + parser.add_argument('secret_value', nargs='?', default=None, help='Value of the secret (required for create and update)') + # Parse command-line arguments + args = parser.parse_args() + + # Set the Secrets Manager client with the AWS region specified by the user + # This allows the client to interact with Secrets Manager in the specified region + session = boto3.session.Session() + client = session.client( + service_name='secretsmanager', + region_name=args.region_name # Dynamically set the region based on user input + ) + + # Check if secret_value is provided and if it's a JSON file path + secret_value = args.secret_value + if secret_value and secret_value.endswith('.json'): + try: + # If a JSON file is provided, read and convert it to a JSON string + with open(secret_value, 'r') as f: + secret_value = json.dumps(json.load(f)) + except FileNotFoundError: + # Handle case when the JSON file is not found + print(f"Error: File {secret_value} not found.") + exit(1) + + # Execute the chosen operation based on the command-line input + if args.operation == 'create_secret': + create_secret(args.secret_name, secret_value) + elif args.operation == 'get_secret': + get_secret(args.secret_name) + elif args.operation == 'update_secret': + update_secret(args.secret_name, secret_value) + elif args.operation == 'delete_secret': + delete_secret(args.secret_name) diff --git a/platforms/shared/charts/bevel-scripts/scripts/package-manager.sh b/platforms/shared/charts/bevel-scripts/scripts/package-manager.sh index 3d48fe5ebc8..5f4c29e99d4 100644 --- a/platforms/shared/charts/bevel-scripts/scripts/package-manager.sh +++ b/platforms/shared/charts/bevel-scripts/scripts/package-manager.sh @@ -59,7 +59,7 @@ install_packages() { case "$PACKAGE_MNG" in apt-get) apt-get update - apt-get install -y $PACKAGES + DEBIAN_FRONTEND=noninteractive apt-get install -y $PACKAGES ;; apk) apk update diff --git a/platforms/shared/charts/bevel-scripts/templates/configmap.yaml b/platforms/shared/charts/bevel-scripts/templates/configmap.yaml index b23cd032803..0b9d7060b0a 100644 --- a/platforms/shared/charts/bevel-scripts/templates/configmap.yaml +++ b/platforms/shared/charts/bevel-scripts/templates/configmap.yaml @@ -4,6 +4,18 @@ # SPDX-License-Identifier: Apache-2.0 ############################################################################################## --- +{{- if .Values.global.cluster.cloudNativeServices }} +{{- if eq .Values.global.cluster.provider "aws" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: aws-secret-manager-script + namespace: {{ .Release.Namespace }} +data: +{{ (.Files.Glob "scripts/aws-secret-manager-script.py").AsConfig | indent 2 }} +{{- end }} +{{- else }} +{{- if eq .Values.global.vault.type "hashicorp" }} apiVersion: v1 kind: ConfigMap metadata: @@ -11,6 +23,8 @@ metadata: namespace: {{ .Release.Namespace }} data: {{ (.Files.Glob "scripts/bevel-vault.sh").AsConfig | indent 2 }} +{{- end }} +{{- end }} --- apiVersion: v1 diff --git a/platforms/shared/charts/bevel-vault-mgmt/templates/serviceAccount.yaml b/platforms/shared/charts/bevel-vault-mgmt/templates/serviceAccount.yaml index e80946bb079..9b4522f79f0 100644 --- a/platforms/shared/charts/bevel-vault-mgmt/templates/serviceAccount.yaml +++ b/platforms/shared/charts/bevel-vault-mgmt/templates/serviceAccount.yaml @@ -1,9 +1,14 @@ -{{- if not .Values.global.cluster.cloudNativeServices }} apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.global.serviceAccountName }} namespace: {{ .Release.Namespace }} +{{- if .Values.global.cluster.cloudNativeServices }} +{{- if eq .Values.global.cluster.provider "aws" }} + annotations: + eks.amazonaws.com/role-arn: {{ .Values.global.cluster.secretManagerArn }} +{{- end }} +{{- else }} {{- if eq .Values.global.vault.type "hashicorp" }} --- apiVersion: rbac.authorization.k8s.io/v1