diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 02e4ef67a..000000000 --- a/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Environment file -.env - -# subproject used for testing: -v1_compatibility/stellar-relief-backoffice-backend - -# Project binary: -stellar-disbursement-platform-backend - -# Text Editors -.vscode diff --git a/resources/aws/cloudformation/.out.swp b/resources/aws/cloudformation/.out.swp new file mode 100644 index 000000000..62c01d51e Binary files /dev/null and b/resources/aws/cloudformation/.out.swp differ diff --git a/resources/aws/cloudformation/README.md b/resources/aws/cloudformation/README.md new file mode 100644 index 000000000..1308a5c9d --- /dev/null +++ b/resources/aws/cloudformation/README.md @@ -0,0 +1,442 @@ +# # Stellar Disbursement Platform (SDP) AWS Kubernetes (EKS) Deployment Guide + +## Prerequisites +- AWS CLI installed and configured +- Helm installed +- kubectl configured to connect to your cluster + +## Cloudformation Stacks +This guide walks through deploying the Stellar Disbursement Platform (SDP) infrastructure on AWS. The deployment consists of four CloudFormation stacks that create the necessary infrastructure in a specific order: + +- Network Stack (sdp-network-eks.yaml) + - Creates or uses existing VPC and subnets + - Sets up networking for both public and private resources + - Exports used (imported) by database and EKS stack to deploy resources + +- Database Stack (sdp-database-eks.yaml) + - Deploys RDS PostgreSQL database in private subnet + - Creates necessary database secrets in AWS Secrets Manager + +- Keys Stack (sdp-keys-eks.yaml) [Optional] + - Manages Stellar and encryption keys by either: + - Using provided keys via parameters, or + - Auto-generating keys using Lambda function for dev/test environments + - Stores all keys and secrets in AWS Secrets Manager under /sdp/${env}/ path + - Keys include SEP-10 signing keys, distribution account keys, JWT secrets, etc. + +- EKS Stack (sdp-eks.yaml) + - Creates EKS cluster and node group + - Sets up IAM roles and security groups + - Configures IRSA (IAM Roles for Service Accounts) + - Sets up permissions for pods to access secrets stored in AWS Secrets Manager + +After the CloudFormation stacks are deployed, additional Kubernetes resources are installed via Helm charts to complete the setup. The SDP expects secrets to be available as Kubernetes secrets, but how those secrets are synchronized (whether through ExternalSecrets, direct creation, or other means) is left to the deployer's preference. + +Note: Both the Keys stack and ExternalSecrets are optional implementation choices. You can manage and sync secrets to Kubernetes secrets through whatever mechanism best fits your security requirements and operational preferences. +##Verify AWS CLI Configuration +```bash +aws configure list +aws sts get-caller-identity +``` + +## 1. Network Stack Deployment +Deploy the networking infrastructure: + +```bash +aws cloudformation create-stack \ + --stack-name sdp-network \ + --template-body file://sdp-network-eks.yaml \ + --parameters \ + ParameterKey=env,ParameterValue=dev +``` + +**Note**: To use existing network resources, provide the VPC and subnet IDs: +```bash +aws cloudformation create-stack \ + --stack-name sdp-network \ + --template-body file://sdp-network-eks.yaml \ + --parameters \ + ParameterKey=env,ParameterValue=dev \ + ParameterKey=ExistingVPCId,ParameterValue=vpc-1234567890abcdef0 \ + ParameterKey=ExistingPublicSubnet1Id,ParameterValue=subnet-xxxxx \ + ParameterKey=ExistingPublicSubnet2Id,ParameterValue=subnet-yyyyy \ + ParameterKey=ExistingPrivateSubnet1Id,ParameterValue=subnet-aaaaa \ + ParameterKey=ExistingPrivateSubnet2Id,ParameterValue=subnet-bbbbb +``` + +Wait for stack completion: +```bash +aws cloudformation wait stack-create-complete --stack-name sdp-network +``` + +## 2. Database Stack Deployment +Deploy the RDS database: + +```bash +aws cloudformation create-stack \ + --stack-name sdp-database \ + --template-body file://sdp-database-eks.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters \ + ParameterKey=env,ParameterValue=dev \ + ParameterKey=NetworkStackName,ParameterValue=sdp-network \ + ParameterKey=DBInstanceClass,ParameterValue=db.t3.small \ + ParameterKey=DBUsername,ParameterValue=postgres \ + ParameterKey=DBPassword,ParameterValue=your-secure-password \ + ParameterKey=MultiAZ,ParameterValue=false +``` + +Wait for stack completion: +```bash +aws cloudformation wait stack-create-complete --stack-name sdp-database +``` + +## 3. Keys Stack Deployment +Deploy the secrets and keys management stack: + +```bash +aws cloudformation create-stack \ + --stack-name sdp-keys-eks \ + --template-body file://sdp-keys-eks.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters \ + ParameterKey=env,ParameterValue=dev \ + ParameterKey=namespace,ParameterValue=sdp \ + ParameterKey=RecaptchaSiteSecretKey,ParameterValue=YOUR_RECAPTCHA_SITE_SECRET_KEY \ + ParameterKey=RecaptchaSiteKey,ParameterValue=YOUR_RECAPTCHA_SITE_KEY +``` + +**Note**: Leave the following parameters empty to auto-generate new keys: +- DistributionSeed +- DistributionPublicKey +- SEP10SigningPrivateKey +- SEP10SigningPublicKey +- ChannelAccountEncryptionPassphrase +- DistributionAccountEncryptionPassphrase + +Wait for stack completion: +```bash +aws cloudformation wait stack-create-complete --stack-name sdp-keys +``` + +## 4. EKS Cluster Deployment +Deploy the EKS cluster: + +```bash +aws cloudformation create-stack \ + --stack-name sdp-eks \ + --template-body file://sdp-eks.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters \ + ParameterKey=env,ParameterValue=dev \ + ParameterKey=NetworkStackName,ParameterValue=sdp-network \ + ParameterKey=DatabaseStackName,ParameterValue=sdp-database +``` + +Wait for stack completion (this will take ~15-20 minutes): +```bash +aws cloudformation wait stack-create-complete --stack-name sdp-eks +``` + +## 5. Configure kubectl +After the EKS cluster is created, configure kubectl: + +```bash +aws eks update-kubeconfig --name $(aws cloudformation describe-stacks \ + --stack-name sdp-eks \ + --query 'Stacks[0].Outputs[?OutputKey==`ClusterName`].OutputValue' \ + --output text) \ + --region your-region +``` + +## 6. Follow Manual Helm Deployment Steps +Continue with the manual Helm deployment steps as provided in the deployment guide, which includes: +1. External Secrets Operator installation +2. AWS Secrets Manager access configuration +3. External Secrets creation +4. Nginx Ingress Controller installation +5. Cert-Manager installation +6. External-DNS setup +7. SDP Helm chart deployment + +Refer to the Helm deployment instructions for these steps. + +## Verification Steps + +### Check Network Stack +```bash +aws cloudformation describe-stacks --stack-name sdp-network \ + --query 'Stacks[0].Outputs' +``` + +### Check Database Connectivity +```bash +aws cloudformation describe-stacks --stack-name sdp-database \ + --query 'Stacks[0].Outputs' +``` + +### Verify EKS Cluster +```bash +aws eks describe-cluster \ + --name $(aws cloudformation describe-stacks \ + --stack-name sdp-eks \ + --query 'Stacks[0].Outputs[?OutputKey==`ClusterName`].OutputValue' \ + --output text) +``` + +### Check Secrets in Secrets Manager +```bash +aws secretsmanager list-secrets \ + --filters Key=name-prefix,Values=/sdp/dev +``` + + +## Initial Setup +```bash +# Configure kubectl for your cluster +aws eks update-kubeconfig --name $(aws cloudformation describe-stacks \ + --stack-name sdp-eks \ + --query 'Stacks[0].Outputs[?OutputKey==`ClusterName`].OutputValue' \ + --output text) + +# Create namespace +kubectl create namespace sdp +``` + +## 1. External Secrets Operator Installation +```bash +# Create external-secrets namespace +kubectl create namespace external-secrets + +# Add and update Helm repository +helm repo add external-secrets https://charts.external-secrets.io +helm repo update + +# Install External Secrets Operator +helm install external-secrets external-secrets/external-secrets \ + --namespace external-secrets \ + --create-namespace \ + --set installCRDs=true \ + --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=$(aws cloudformation describe-stacks \ + --stack-name sdp-eks \ + --query 'Stacks[0].Outputs[?OutputKey==`ExternalSecretsOperatorRoleArn`].OutputValue' \ + --output text) + +# Verify installation +kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=external-secrets -n external-secrets --timeout=120s +``` + +## 2. Configure AWS Secrets Manager Access +```bash +# Set role ARN +export SECRETSTORE_ROLE_ARN=$(aws cloudformation describe-stacks \ + --stack-name sdp-eks \ + --query 'Stacks[0].Outputs[?OutputKey==`SecretStoreRoleArn`].OutputValue' \ + --output text) + +# Create ServiceAccount and SecretStore +cat < + +# Check pod details +kubectl describe pods -n sdp +``` diff --git a/resources/aws/cloudformation/eks-helm/cluster-issuer.yaml b/resources/aws/cloudformation/eks-helm/cluster-issuer.yaml new file mode 100644 index 000000000..956c3bed0 --- /dev/null +++ b/resources/aws/cloudformation/eks-helm/cluster-issuer.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: reece@stellar.org + privateKeySecretRef: + name: letsencrypt-prod + solvers: + - dns01: + route53: + region: us-west-2 \ No newline at end of file diff --git a/resources/aws/cloudformation/eks-helm/sdp-secrets-dev.yaml b/resources/aws/cloudformation/eks-helm/sdp-secrets-dev.yaml new file mode 100644 index 000000000..d63d6cc93 --- /dev/null +++ b/resources/aws/cloudformation/eks-helm/sdp-secrets-dev.yaml @@ -0,0 +1,86 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: sdp-secrets + namespace: sdp +spec: + refreshInterval: 1h + secretStoreRef: + name: aws-backend + kind: SecretStore + target: + name: sdp-secrets + creationPolicy: Owner + data: + - secretKey: SECRET_DATA_USERNAME + remoteRef: + key: /sdp/dev/SECRET_DATA_USERNAME + - secretKey: SECRET_DATA_PASSWORD + remoteRef: + key: /sdp/dev/SECRET_DATA_PASSWORD + - secretKey: DATA_SERVER + remoteRef: + key: /sdp/dev/DATA_SERVER + - secretKey: DISTRIBUTION_SEED + remoteRef: + key: /sdp/dev/DISTRIBUTION_SEED + - secretKey: DISTRIBUTION_PUBLIC_KEY + remoteRef: + key: /sdp/dev/DISTRIBUTION_PUBLIC_KEY + - secretKey: SEP10_SIGNING_PRIVATE_KEY + remoteRef: + key: /sdp/dev/SEP10_SIGNING_PRIVATE_KEY + - secretKey: SEP10_SIGNING_PUBLIC_KEY + remoteRef: + key: /sdp/dev/SEP10_SIGNING_PUBLIC_KEY + - secretKey: SECRET_SEP10_SIGNING_SEED + remoteRef: + key: /sdp/dev/SECRET_SEP10_SIGNING_SEED + - secretKey: EC256_PRIVATE_KEY + remoteRef: + key: /sdp/dev/EC256_PRIVATE_KEY + - secretKey: EC256_PUBLIC_KEY + remoteRef: + key: /sdp/dev/EC256_PUBLIC_KEY + - secretKey: CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE + remoteRef: + key: /sdp/dev/CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE + - secretKey: DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE + remoteRef: + key: /sdp/dev/DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE + - secretKey: SECRET_PLATFORM_API_AUTH_SECRET + remoteRef: + key: /sdp/dev/SECRET_PLATFORM_API_AUTH_SECRET + - secretKey: SECRET_SEP10_JWT_SECRET + remoteRef: + key: /sdp/dev/SECRET_SEP10_JWT_SECRET + - secretKey: SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET + remoteRef: + key: /sdp/dev/SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET + - secretKey: SECRET_SEP24_MORE_INFO_URL_JWT_SECRET + remoteRef: + key: /sdp/dev/SECRET_SEP24_MORE_INFO_URL_JWT_SECRET + - secretKey: DATABASE_URL + remoteRef: + key: /sdp/dev/DATABASE_URL + - secretKey: SEP24_JWT_SECRET + remoteRef: + key: /sdp/dev/SEP24_JWT_SECRET + - secretKey: ANCHOR_PLATFORM_OUTGOING_JWT_SECRET + remoteRef: + key: /sdp/dev/ANCHOR_PLATFORM_OUTGOING_JWT_SECRET + - secretKey: RECAPTCHA_SITE_KEY + remoteRef: + key: /sdp/dev/RECAPTCHA_SITE_KEY + - secretKey: RECAPTCHA_SITE_SECRET_KEY + remoteRef: + key: /sdp/dev/RECAPTCHA_SITE_SECRET_KEY + - secretKey: ADMIN_API_KEY + remoteRef: + key: /sdp/dev/ADMIN_API_KEY + - secretKey: AWS_ACCESS_KEY_ID + remoteRef: + key: /sdp/dev/AWS_ACCESS_KEY_ID + - secretKey: AWS_SECRET_ACCESS_KEY + remoteRef: + key: /sdp/dev/AWS_SECRET_ACCESS_KEY diff --git a/resources/aws/cloudformation/eks-helm/values-dev.yaml b/resources/aws/cloudformation/eks-helm/values-dev.yaml new file mode 100644 index 000000000..5b76d9cf2 --- /dev/null +++ b/resources/aws/cloudformation/eks-helm/values-dev.yaml @@ -0,0 +1,253 @@ +global: + isPubnet: false + ephemeralDatabase: false + eventBroker: + type: "NONE" + urls: "kafka:9092" + consumerGroupId: "group-id" + +sdp: + route: + domain: sdp-backend.mystellarsdpdomain.org + mtnDomain: "*.sdp-backend.mystellarsdpdomain.org" + deployment: + podAnnotations: + prometheus.io/path: /metrics + prometheus.io/port: '{{ include "sdp.metricsPort" . }}' + prometheus.io/scrape: "true" + strategy: + # Ensure we upgrade 1 pod at a time to avoid migration races + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + serviceAccount: + create: true + name: sdp-service-account + annotations: + eks.amazonaws.com/role-arn: ${SERVICE_ACCOUNT_ROLE_ARN} + # =========================== START sdp.kubeSecrets =========================== + kubeSecrets: + secretName: sdp-secrets + create: false + data: + EC256_PRIVATE_KEY: "/sdp/dev/EC256_PRIVATE_KEY" + EC256_PUBLIC_KEY: "/sdp/dev/EC256_PUBLIC_KEY" + SEP10_SIGNING_PRIVATE_KEY: "/sdp/dev/SEP10_SIGNING_PRIVATE_KEY" + SEP10_SIGNING_PUBLIC_KEY: "/sdp/dev/SEP10_SIGNING_PUBLIC_KEY" + SEP24_JWT_SECRET: "/sdp/dev/SEP24_JWT_SECRET" + DISTRIBUTION_SEED: "/sdp/dev/DISTRIBUTION_SEED" + DISTRIBUTION_PUBLIC_KEY: "/sdp/dev/DISTRIBUTION_PUBLIC_KEY" + DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE: "/sdp/dev/DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE" + CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE: "/sdp/dev/channel-encryption-passphrase" + DATABASE_URL: "/sdp/dev/DATABASE_URL" + RECAPTCHA_SITE_KEY: "/sdp/dev/RECAPTCHA_SITE_KEY" + RECAPTCHA_SITE_SECRET_KEY: "/sdp/dev/RECAPTCHA_SITE_SECRET_KEY" + AWS_SECRET_ACCESS_KEY: "/sdp/dev/AWS_SECRET_ACCESS_KEY" + AWS_ACCESS_KEY_ID: "/sdp/dev/AWS_ACCESS_KEY_ID" + # =========================== START sdp.configMap =========================== + configMap: + data: + AWS_SNS_SENDER_ID: "" + AWS_REGION: "us-west-2" + ADMIN_ACCOUNT: "reece@stellar.org" + ENVIRONMENT: "dev" + LOG_LEVEL: "debug" + METRICS_TYPE: "PROMETHEUS" + EMAIL_SENDER_TYPE: "DRY_RUN" + SMS_SENDER_TYPE: "AWS_SMS" + ENABLE_MFA: "false" + NETWORK_PASSPHRASE: "Test SDF Network ; September 2015" + HORIZON_URL: "https://horizon-testnet.stellar.org" + INSTANCE_NAME: "Stellar Disbursement Platform" + CORS_ALLOWED_ORIGINS: "*" + ENABLE_SCHEDULER: "true" + SCHEDULER_RECEIVER_INVITATION_JOB_SECONDS: "10" + SCHEDULER_PAYMENT_JOB_SECONDS: "10" + SDP_UI_BASE_URL: "https://dashboard.mystellarsdpdomain.org" + # =========================== START sdp.ingress =========================== + ingress: + enabled: true + className: "ingress-public" + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + tls: + - hosts: + - 'sdp-backend.mystellarsdpdomain.org' + - '*.sdp-backend.mystellarsdpdomain.org' + secretName: sdp-backend-cert +# ============================= anchorPlatform =================================== +anchorPlatform: + route: + domain: ap-sdp-backend.mystellarsdpdomain.org + deployment: + podAnnotations: + prometheus.io/path: /metrics + prometheus.io/port: '{{ include "sdp.ap.metricsPort" . }}' + prometheus.io/scrape: "true" + strategy: + # Ensure we upgrade 1 pod at a time to avoid migration races + type: "RollingUpdate" + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + kubeSecrets: + create: false + secretName: sdp-secrets + data: + SECRET_DATA_USERNAME: "/sdp/dev/SECRET_DATA_USERNAME" + SECRET_DATA_PASSWORD: "/sdp/dev/SECRET_DATA_PASSWORD" + DATA_SERVER: "/sdp/dev/DATA_SERVER" + SECRET_SEP10_SIGNING_SEED: "/sdp/dev/SEP10_SIGNING_PRIVATE_KEY" + SECRET_PLATFORM_API_AUTH_SECRET: "/sdp/dev/SECRET_PLATFORM_API_AUTH_SECRET" + SECRET_SEP10_JWT_SECRET: "/sdp/dev/SECRET_SEP10_JWT_SECRET" + SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET: "/sdp/dev/SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET" + SECRET_SEP24_MORE_INFO_URL_JWT_SECRET: "/sdp/dev/SECRET_SEP24_MORE_INFO_URL_JWT_SECRET" + DISTRIBUTION_PUBLIC_KEY: "/sdp/dev/DISTRIBUTION_PUBLIC_KEY" + serviceAccount: + create: true + name: anchor-service-account + annotations: + eks.amazonaws.com/role-arn: ${SERVICE_ACCOUNT_ROLE_ARN} + configMap: + data: + HOST_URL: https://ap-sdp-backend.mystellarsdpdomain.org + SEP_SERVER_PORT: "8080" + CALLBACK_API_BASE_URL: https://sdp-backend.mystellarsdpdomain.org + CALLBACK_API_AUTH_TYPE: "none" + PLATFORM_SERVER_AUTH_TYPE: "JWT" + APP_LOGGING_LEVEL: "INFO" + DATA_TYPE: "postgres" + DATA_DATABASE: "sdp_dev" + DATA_FLYWAY_ENABLED: "true" + DATA_DDL_AUTO: "update" + METRICS_ENABLED: "false" + METRICS_EXTRAS_ENABLED: "false" + EVENT_BROKER_TYPE: "NONE" + SEP10_ENABLED: "true" + SEP10_WEB_AUTH_DOMAIN: ap-sdp-backend.mystellarsdpdomain.org + SEP10_HOME_DOMAINS: "*.sdp-backend.mystellarsdpdomain.org" + SEP24_ENABLED: "true" + SEP24_INTERACTIVE_URL_BASE_URL: https://sdp-backend.mystellarsdpdomain.org/wallet-registration/start + SEP24_INTERACTIVE_URL_JWT_EXPIRATION: "1800" + SEP24_MORE_INFO_URL_BASE_URL: https://sdp-backend.mystellarsdpdomain.org/wallet-registration/start + SEP1_ENABLED: "true" + SEP1_TOML_TYPE: "url" + SEP1_TOML_VALUE: https://sdp-backend.mystellarsdpdomain.org/.well-known/stellar.toml + ASSETS_TYPE: "json" + ASSETS_VALUE: | + { + "assets": [ + { + "sep24_enabled": true, + "schema": "stellar", + "code": "USDC", + "issuer": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + "distribution_account": "NOT_APPLICABLE", + "significant_decimals": 7, + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 10000 + }, + "withdraw": {"enabled": false} + }, + { + "sep24_enabled": true, + "schema": "stellar", + "code": "native", + "distribution_account": "NOT_APPLICABLE", + "significant_decimals": 7, + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 10000 + }, + "withdraw": {"enabled": false} + } + ] + } + ingress: + enabled: true + className: "ingress-public" + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + tls: + - hosts: + - 'ap-sdp-backend.mystellarsdpdomain.org' + secretName: sdp-ap-cert + +# ============================= tss =================================== +tss: + deployment: + podAnnotations: + prometheus.io/path: /metrics + prometheus.io/port: '{{ include "tss.metricsPort" . }}' + prometheus.io/scrape: "true" + strategy: + type: "RollingUpdate" + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + kubeSecrets: + create: false + secretName: sdp-secrets + data: + DISTRIBUTION_PUBLIC_KEY: "/sdp/dev/DISTRIBUTION_PUBLIC_KEY" + DISTRIBUTION_SEED: "/sdp/dev/DISTRIBUTION_SEED" + DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE: "/sdp/dev/DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE" + CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE: "/sdp/dev/CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE" + DATABASE_URL: "/sdp/dev/DATABASE_URL" + serviceAccount: + create: true + name: tss-service-account + annotations: + eks.amazonaws.com/role-arn: ${SERVICE_ACCOUNT_ROLE_ARN} + configMap: + data: + LOG_LEVEL: "info" + NETWORK_PASSPHRASE: "Test SDF Network ; September 2015" + HORIZON_URL: "https://horizon-testnet.stellar.org" + NUM_CHANNEL_ACCOUNTS: "3" + MAX_BASE_FEE: "100" + TSS_METRICS_PORT: "9002" + TSS_METRICS_TYPE: "TSS_PROMETHEUS" + EVENT_BROKER_TYPE: "NONE" + BROKER_URLS: "kafka:9092" + CONSUMER_GROUP_ID: "group-id" + KAFKA_SECURITY_PROTOCOL: "PLAINTEXT" +# ============================= dashboard =================================== +dashboard: + enabled: true + route: + domain: "dashboard.mystellarsdpdomain.org" + mtnDomain: "*.dashboard.mystellarsdpdomain.org" + deployment: + strategy: + type: "RollingUpdate" + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + env: + - name: RECAPTCHA_SITE_KEY + valueFrom: + secretKeyRef: + name: sdp-secrets + key: RECAPTCHA_SITE_KEY + configMap: + data: + RECAPTCHA_SITE_KEY: 6LcArsEqAAAAAIxEcbaHI4HTp7I5C1RrTv6LC9-Y + API_URL: https://sdp-backend.mystellarsdpdomain.org + ingress: + enabled: true + className: "ingress-public" + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + tls: + - hosts: + - 'dashboard.mystellarsdpdomain.org' + - '*.dashboard.mystellarsdpdomain.org' + secretName: sdp-dashboard-cert diff --git a/resources/aws/cloudformation/sdp-database-eks.yaml b/resources/aws/cloudformation/sdp-database-eks.yaml new file mode 100644 index 000000000..1fbfb32a9 --- /dev/null +++ b/resources/aws/cloudformation/sdp-database-eks.yaml @@ -0,0 +1,279 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: RDS Database Stack for SDP on EKS + +Parameters: + NetworkStackName: + Type: String + Default: sdp-network + Description: Name of the network stack to import VPC and subnet values from + + env: + Type: String + Default: "dev" + AllowedValues: + - dev + - staging + - prod + Description: Environment name + + namespace: + Type: String + Description: "Kubernetes namespace where SDP will be deployed" + Default: "sdp" + + DBInstanceClass: + Type: String + Default: db.t3.small + AllowedValues: + - db.t3.small + - db.t3.medium + - db.t3.large + - db.r5.large + Description: Database instance size + + DBAllocatedStorage: + Type: Number + Default: 20 + MinValue: 20 + MaxValue: 1000 + Description: Size of database storage in GB + + DBUsername: + Type: String + Default: postgres + MinLength: 1 + MaxLength: 16 + AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*" + ConstraintDescription: Must begin with a letter and contain only alphanumeric characters + Description: Database admin username + + BackupRetentionPeriod: + Type: Number + Default: 7 + MinValue: 0 + MaxValue: 35 + Description: Number of days to retain automated backups + + MultiAZ: + Type: String + Default: false + AllowedValues: + - true + - false + Description: Enable Multi-AZ deployment + + DeletionProtection: + Type: String + Default: false + AllowedValues: + - true + - false + Description: Enable deletion protection + + DBPassword: + Type: String + Default: postgres + NoEcho: true + MinLength: 8 + Description: Password for database admin user + +Resources: + RDSSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for RDS instance + VpcId: + Fn::ImportValue: !Sub ${NetworkStackName}-vpc-id + SecurityGroupEgress: + - IpProtocol: -1 + FromPort: -1 + ToPort: -1 + CidrIp: 0.0.0.0/0 + Tags: + - Key: Name + Value: !Sub ${env}-rds-security-group + - Key: Environment + Value: !Ref env + + DBSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/db/credentials + Description: RDS database credentials and connection information + SecretString: !Sub '{"username": "${DBUsername}", "password": "${DBPassword}", "dbname": "sdp_${env}", "port": 5432, "host": "${PostgresInstance.Endpoint.Address}"}' + + DatabaseURLSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/db/url + Description: Complete database connection URL + SecretString: !Sub '{"DATABASE_URL": "postgres://${DBUsername}:${DBPassword}@${PostgresInstance.Endpoint.Address}:${PostgresInstance.Endpoint.Port}/sdp_${env}"}' + + DatabaseHostSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/db/host + Description: Database host + SecretString: !GetAtt PostgresInstance.Endpoint.Address + + DatabasePortSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/db/port + Description: Database port + SecretString: !GetAtt PostgresInstance.Endpoint.Port + + DatabaseNameSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/db/name + Description: Database name + SecretString: !Sub sdp_${env} + + SecretDataUsernameSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/${env}/SECRET_DATA_USERNAME + Description: Anchor Platform DATA_USER + SecretString: !Ref DBUsername + + SecretDataPasswordSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/${env}/SECRET_DATA_PASSWORD + Description: Anchor Platform SECRET_DATA_PASSWORD + SecretString: !Ref DBPassword + + DataServerSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/${env}/DATA_SERVER + Description: Anchor Platform DATA_SERVER + SecretString: !GetAtt PostgresInstance.Endpoint.Address + + DatabaseURLSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/${env}/DATABASE_URL + Description: Complete database connection URL + SecretString: !Sub "postgres://${DBUsername}:${DBPassword}@${PostgresInstance.Endpoint.Address}:${PostgresInstance.Endpoint.Port}/sdp_${env}" + + DBSubnetGroup: + Type: AWS::RDS::DBSubnetGroup + Properties: + DBSubnetGroupDescription: !Sub ${env}-database-subnet-group + SubnetIds: + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-1 + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-2 + Tags: + - Key: Name + Value: !Sub ${env}-database-subnet-group + - Key: Environment + Value: !Ref env + + DBParameterGroup: + Type: AWS::RDS::DBParameterGroup + Properties: + Family: postgres14 + Description: Custom parameter group for SDP database + Parameters: + max_connections: "50" + shared_buffers: "4096" + ssl: "1" + Tags: + - Key: Environment + Value: !Ref env + + PostgresInstance: + Type: AWS::RDS::DBInstance + Properties: + DBName: !Sub sdp_${env} + Engine: postgres + EngineVersion: 14 + DBInstanceClass: !Ref DBInstanceClass + AllocatedStorage: !Ref DBAllocatedStorage + StorageType: gp2 + StorageEncrypted: true + MultiAZ: !Ref MultiAZ + PubliclyAccessible: false + DeletionProtection: !Ref DeletionProtection + DBSubnetGroupName: !Ref DBSubnetGroup + VPCSecurityGroups: + - !Ref RDSSecurityGroup + BackupRetentionPeriod: !Ref BackupRetentionPeriod + DBParameterGroupName: !Ref DBParameterGroup + MasterUsername: !Ref DBUsername + MasterUserPassword: !Ref DBPassword + MonitoringInterval: 0 + AutoMinorVersionUpgrade: true + CopyTagsToSnapshot: true + Tags: + - Key: Name + Value: !Sub ${env}-sdp-database + - Key: Environment + Value: !Ref env + + DBEndpointParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /sdp/${env}/DB_ENDPOINT + Type: String + Value: !GetAtt PostgresInstance.Endpoint.Address + Description: Database endpoint + Tags: + Environment: !Ref env + + DBPortParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /sdp/${env}/DB_PORT + Type: String + Value: !GetAtt PostgresInstance.Endpoint.Port + Description: Database port + Tags: + Environment: !Ref env + + DBNameParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /sdp/${env}/DB_NAME + Type: String + Value: !Sub sdp_${env} + Description: Database name + Tags: + Environment: !Ref env + +Outputs: + DatabaseEndpoint: + Description: Database endpoint + Value: !GetAtt PostgresInstance.Endpoint.Address + Export: + Name: !Sub ${AWS::StackName}-db-endpoint + + DatabasePort: + Description: Database port + Value: !GetAtt PostgresInstance.Endpoint.Port + Export: + Name: !Sub ${AWS::StackName}-db-port + + DatabaseName: + Description: Database name + Value: !Sub sdp_${env} + Export: + Name: !Sub ${AWS::StackName}-db-name + + DatabaseSecretArn: + Value: !Ref DBSecret + Export: + Name: !Sub ${AWS::StackName}-db-secret-arn + + DatabaseUrlSecret: + Value: !Ref DatabaseURLSecret + Export: + Name: !Sub ${AWS::StackName}-database-url-secret + + DatabaseSecurityGroup: + Description: Security Group ID for RDS instance + Value: !Ref RDSSecurityGroup + Export: + Name: !Sub ${AWS::StackName}-db-sg diff --git a/resources/aws/cloudformation/sdp-eks.yaml b/resources/aws/cloudformation/sdp-eks.yaml new file mode 100644 index 000000000..93143d7cc --- /dev/null +++ b/resources/aws/cloudformation/sdp-eks.yaml @@ -0,0 +1,511 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'EKS Cluster for SDP' + +Parameters: + env: + Type: String + Default: "dev" + AllowedValues: + - dev + - staging + - prod + + NetworkStackName: + Type: String + Default: sdp-network + Description: Name of the networking stack + + DatabaseStackName: + Type: String + Default: sdp-database + Description: Name of the database stack used to create security group + +Resources: + ######################################################################## + # EKS Cluster + ######################################################################## + EKSCluster: + Type: AWS::EKS::Cluster + Properties: + Name: !Sub "${env}-sdp-cluster" + Version: "1.27" + RoleArn: !GetAtt EKSClusterRole.Arn + ResourcesVpcConfig: + SecurityGroupIds: + - !Ref EKSClusterSecurityGroup + SubnetIds: + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-1 + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-2 + Logging: + ClusterLogging: + EnabledTypes: [] + + ######################################################################## + # OIDC Provider for IRSA + ######################################################################## + OIDCProvider: + Type: AWS::IAM::OIDCProvider + DependsOn: [EKSCluster, ClusterOIDCId] + Properties: + Url: !Sub "https://oidc.eks.${AWS::Region}.amazonaws.com/id/${ClusterOIDCId.OIDCId}" + ClientIdList: + - "sts.amazonaws.com" + ThumbprintList: + - "9e99a48a9960b14926bb7f3b02e22da2b0ab7280" + + ######################################################################## + # NodeGroup + ######################################################################## + + EKSNodeGroup: # Changed from EKSNodeGroup + Type: AWS::EKS::Nodegroup + Properties: + ClusterName: !Ref EKSCluster + NodeRole: !GetAtt EKSNodeGroupRole.Arn + ScalingConfig: + MinSize: 2 + DesiredSize: 3 + MaxSize: 4 + InstanceTypes: + - t3.small + Subnets: + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-1 + - Fn::ImportValue: !Sub ${NetworkStackName}-private-subnet-2 + + ######################################################################## + # IAM Roles + ######################################################################## + EKSClusterRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: eks.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy + + CertManagerRole: + Type: AWS::IAM::Role + DependsOn: [OIDCProvider, ClusterOIDCId] + Properties: + RoleName: !Sub "${env}-cert-manager-role" + AssumeRolePolicyDocument: + Fn::Sub: + - | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:sub": "system:serviceaccount:cert-manager:cert-manager", + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:aud": "sts.amazonaws.com" + } + } + } + ] + } + - OIDCId: !GetAtt ClusterOIDCId.OIDCId + Policies: + - PolicyName: Route53Access + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'route53:GetChange' + Resource: 'arn:aws:route53:::change/*' + - Effect: Allow + Action: + - 'route53:ChangeResourceRecordSets' + - 'route53:ListResourceRecordSets' + Resource: 'arn:aws:route53:::hostedzone/*' + - Effect: Allow + Action: 'route53:ListHostedZonesByName' + Resource: '*' + + EKSNodeGroupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: ec2.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + Policies: + - PolicyName: SecretsAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: + - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/sdp/* + + ExternalDNSPolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + Description: Policy for external-dns to manage Route53 records + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - route53:ChangeResourceRecordSets + Resource: + - !Sub "arn:aws:route53:::hostedzone/*" + - Effect: Allow + Action: + - route53:ListHostedZones + - route53:ListResourceRecordSets + - route53:ListTagsForResource + Resource: ["*"] + + ExternalDNSRole: + Type: AWS::IAM::Role + DependsOn: [OIDCProvider, ClusterOIDCId] + Properties: + RoleName: !Sub "${env}-external-dns-role" + ManagedPolicyArns: + - !Ref ExternalDNSPolicy + AssumeRolePolicyDocument: + Fn::Sub: + - | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:sub": "system:serviceaccount:external-dns:external-dns", + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:aud": "sts.amazonaws.com" + } + } + } + ] + } + - OIDCId: !GetAtt ClusterOIDCId.OIDCId + + ######################################################################## + # Security Group for EKS + ######################################################################## + EKSClusterSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for EKS cluster + VpcId: + Fn::ImportValue: !Sub ${NetworkStackName}-vpc-id + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: + Fn::ImportValue: !Sub ${NetworkStackName}-vpc-cidr + + RDSIngressRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: + Fn::ImportValue: !Sub ${DatabaseStackName}-db-sg # This imports the RDS security group ID + IpProtocol: tcp + FromPort: 5432 + ToPort: 5432 + SourceSecurityGroupId: !Ref EKSClusterSecurityGroup # This references the EKS cluster security group + + NodeToRDSIngressRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: + Fn::ImportValue: !Sub ${DatabaseStackName}-db-sg + IpProtocol: tcp + FromPort: 5432 + ToPort: 5432 + SourceSecurityGroupId: !Ref EKSNodeSecurityGroup + + EKSNodeSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for EKS worker nodes + VpcId: + Fn::ImportValue: !Sub ${NetworkStackName}-vpc-id + Tags: + - Key: Name + Value: !Sub "${env}-sdp-cluster-node-sg" + + EKSCreatedSGToRDSIngressRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: + Fn::ImportValue: !Sub ${DatabaseStackName}-db-sg + IpProtocol: tcp + FromPort: 5432 + ToPort: 5432 + SourceSecurityGroupId: !GetAtt EKSCluster.ClusterSecurityGroupId + + NodeGroupSecurityGroupIngress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref EKSNodeSecurityGroup + SourceSecurityGroupId: !Ref EKSNodeSecurityGroup + IpProtocol: '-1' + FromPort: -1 + ToPort: -1 + + NodeToClusterIngress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref EKSClusterSecurityGroup + SourceSecurityGroupId: !Ref EKSNodeSecurityGroup + IpProtocol: '-1' + FromPort: -1 + ToPort: -1 + + ClusterToNodeIngress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref EKSNodeSecurityGroup + SourceSecurityGroupId: !Ref EKSClusterSecurityGroup + IpProtocol: '-1' + FromPort: -1 + ToPort: -1 + ######################################################################## + # Service Account Role for SDP + ######################################################################## + ExternalSecretsOperatorRole: + Type: AWS::IAM::Role + DependsOn: [OIDCProvider, ClusterOIDCId] + Properties: + RoleName: !Sub "${env}-${AWS::StackName}-eso-role" + AssumeRolePolicyDocument: + Fn::Sub: + - | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:aud": "sts.amazonaws.com", + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:sub": "system:serviceaccount:external-secrets:external-secrets" + } + } + } + ] + } + - OIDCId: !GetAtt ClusterOIDCId.OIDCId + Policies: + - PolicyName: ExternalSecretsAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'secretsmanager:GetSecretValue' + - 'secretsmanager:DescribeSecret' + Resource: + - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/sdp/* + + SDPServiceAccountRole: + Type: AWS::IAM::Role + DependsOn: [OIDCProvider, ClusterOIDCId] + Properties: + RoleName: !Sub "${env}-sdp-service-account" + AssumeRolePolicyDocument: + Fn::Sub: + - | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:aud": "sts.amazonaws.com", + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:sub": "system:serviceaccount:external-secrets:external-secrets" + } + } + } + ] + } + - OIDCId: !GetAtt ClusterOIDCId.OIDCId + Policies: + - PolicyName: SecretsAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: + - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/sdp/* + + ClusterOIDCIdFunction: + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: !GetAtt LambdaExecutionRole.Arn + Code: + ZipFile: | + import boto3 + import cfnresponse + import json + + def handler(event, context): + try: + if event['RequestType'] in ['Create', 'Update']: + eks = boto3.client('eks') + cluster_name = event['ResourceProperties']['ClusterName'] + + response = eks.describe_cluster(name=cluster_name) + issuer_url = response['cluster']['identity']['oidc']['issuer'] + oidc_id = issuer_url.split('/')[-1] + + cfnresponse.send(event, context, cfnresponse.SUCCESS, { + 'OIDCId': oidc_id + }) + else: + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + except Exception as e: + print(e) + cfnresponse.send(event, context, cfnresponse.FAILED, {}) + Runtime: python3.9 + Timeout: 30 + + LambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: EKSDescribe + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: eks:DescribeCluster + Resource: !GetAtt EKSCluster.Arn + + ClusterOIDCId: + Type: Custom::OIDCId + Properties: + ServiceToken: !GetAtt ClusterOIDCIdFunction.Arn + ClusterName: !Ref EKSCluster + + SecretStoreRole: + Type: AWS::IAM::Role + DependsOn: [OIDCProvider, ClusterOIDCId] + Properties: + RoleName: !Sub "${env}-${AWS::StackName}-secretstore-role" + AssumeRolePolicyDocument: + Fn::Sub: + - | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:sub": "system:serviceaccount:sdp:external-secrets-sa", + "oidc.eks.${AWS::Region}.amazonaws.com/id/${OIDCId}:aud": "sts.amazonaws.com" + } + } + } + ] + } + - OIDCId: !GetAtt ClusterOIDCId.OIDCId + Policies: + - PolicyName: SecretsAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'secretsmanager:GetSecretValue' + - 'secretsmanager:DescribeSecret' + Resource: + - !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/sdp/* + +Outputs: + ClusterName: + Description: EKS cluster name + Value: !Ref EKSCluster + Export: + Name: !Sub "${AWS::StackName}-cluster-name" + + ClusterSecurityGroupId: + Description: Security Group ID for EKS cluster + Value: !Ref EKSClusterSecurityGroup + Export: + Name: !Sub "${AWS::StackName}-cluster-sg" + + SecretStoreRoleArn: + Description: IAM Role ARN for SecretStore + Value: !GetAtt SecretStoreRole.Arn + Export: + Name: !Sub "${AWS::StackName}-secretstore-role-arn" + + CertManagerRoleArn: + Description: IAM Role ARN for cert-manager + Value: !GetAtt CertManagerRole.Arn + Export: + Name: !Sub "${AWS::StackName}-cert-manager-role-arn" + + ExternalSecretsOperatorRoleArn: + Description: IAM Role ARN for External Secrets Operator + Value: !GetAtt ExternalSecretsOperatorRole.Arn + Export: + Name: !Sub "${AWS::StackName}-eso-role-arn" + + ServiceAccountRoleArn: + Description: IAM Role ARN for SDP Service Account + Value: !GetAtt SDPServiceAccountRole.Arn + Export: + Name: !Sub "${AWS::StackName}-service-account-role-arn" + + ExternalDNSRoleArn: + Description: IAM Role ARN for external-dns + Value: !GetAtt ExternalDNSRole.Arn + Export: + Name: !Sub "${AWS::StackName}-external-dns-role-arn" \ No newline at end of file diff --git a/resources/aws/cloudformation/sdp-keys-eks.yaml b/resources/aws/cloudformation/sdp-keys-eks.yaml new file mode 100644 index 000000000..f80f2635f --- /dev/null +++ b/resources/aws/cloudformation/sdp-keys-eks.yaml @@ -0,0 +1,587 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Stack for managing Stellar and encryption keys in Secrets Manager for ECS' + +Parameters: + env: + Type: String + Default: "dev" + Description: "Environment variable: ENV" + + namespace: + Type: String + Description: "Environment variable: namespace - Kubernetes namespace where SDP will be deployed" + Default: "sdp" + + StellarLayerS3Bucket: + Type: String + Description: "S3 bucket containing the Stellar SDK Lambda layer" + Default: "stellar-layer" + + Sep10SigningPrivateKey: + Type: String + Default: "" + NoEcho: true + Description: "Environment variable: SEP10_SIGNING_PRIVATE_KEY" + + SecretSep10SigningSeed: + Type: String + Default: "" + NoEcho: true + Description: "Environment variable: Anchor Platform SECRET_SEP10_SIGNING_SEED" + + Sep10SigningPublicKey: + Type: String + Default: "" + Description: "Environment variable: SEP10_SIGNING_PUBLIC_KEY" + + SecretSep10JwtSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment variable: SECRET_SEP10_JWT_SECRET" + + DistributionSeed: + Type: String + Default: "" + NoEcho: true + Description: "Environment variable: DISTRIBUTION_SEED" + + DistributionPublicKey: + Type: String + Default: "" + Description: "Environment variable: DISTRIBUTION_PUBLIC_KEY" + + DistributionAccountEncryptionPassphrase: + Type: String + Default: "" + NoEcho: true + Description: "Environment variable: DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE" + + ChannelAccountEncryptionPassphrase: + Type: String + Default: "" + NoEcho: true + Description: "Environment variable: CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE" + + Ec256PrivateKey: + Type: String + NoEcho: true + Default: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIPRRtyc5EQoNPFhkcDzC47B2Zpo5b0NiM3Ftvky86+bEoAoGCCqGSM49 + AwEHoUQDQgAEWinhVw0QHkZDeZ777zfBKT0cupULkpEd8Y52iPs76AT7JQ1cuGbm + jxJASNwp907KzNzOZJSV07bFdN/Tkwebgg== + -----END EC PRIVATE KEY----- + Description: "Environment variable: EC256_PRIVATE_KEY" + + Ec256PublicKey: + Type: String + Default: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWinhVw0QHkZDeZ777zfBKT0cupUL + kpEd8Y52iPs76AT7JQ1cuGbmjxJASNwp907KzNzOZJSV07bFdN/Tkwebgg== + -----END PUBLIC KEY----- + Description: "Environment variable: EC256_PUBLIC_KEY" + + SecretSep24InteractiveUrlJwtSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment variable: SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET" + + SecretSep24MoreInfoUrlJwtSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment variable: SECRET_SEP24_MORE_INFO_URL_JWT_SECRET" + + Sep24JwtSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment variable: SEP24_JWT_SECRET" + + SecretPlatformApiAuthSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment varible SECRET_PLATFORM_API_AUTH_SECRET" + + AnchorPlatformOutgoingJwtSecret: + Type: String + NoEcho: true + Default: "jwt_secret_1234567890" + Description: "Environment variable: ANCHOR_PLATFORM_OUTGOING_JWT_SECRET" + + AdminApiKey: + Type: String + NoEcho: true + Default: "admin-api-key" + Description: "Environment variable: ADMIN_API_KEY" + + RecaptchaSiteKey: + Type: String + NoEcho: true + Default: "YOUR_RECAPTCHA_KEY" + Description: "Environment variable: RECAPTCHA_SITE_KEY" + + RecaptchaSiteSecretKey: + Type: String + NoEcho: true + Default: "YOUR_RECAPTCHA_SECRET_KEY" + Description: "Environment variable: RECAPTCHA_SITE_SECRET_KEY" + + AwsSnsSenderId: + Type: String + NoEcho: true + Default: "AWS_SNS_SENDER_ID" + Description: "Environment variable: AWS_SNS_SENDER_ID" + + AwsSecretAccessKey: + Type: String + NoEcho: true + Default: "AWS_SECRET_ACCESS_KEY" + Description: "Environment variable: AWS_SECRET_ACCESS_KEY" + + AwsAccessKeyId: + Type: String + NoEcho: true + Default: "AWS_ACCESS_KEY_ID" + Description: "Environment variable: AWS_ACCESS_KEY_ID" + + +Conditions: + GenerateSep10Keys: !Equals [ !Ref Sep10SigningPrivateKey, "" ] + GenerateDistributionKeys: !Equals [ !Ref DistributionSeed, "" ] + GenerateChannelKeys: !Equals [ !Ref ChannelAccountEncryptionPassphrase, "" ] + +Resources: + StellarKeyGenRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: SecretsManagerAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - secretsmanager:CreateSecret + - secretsmanager:PutSecretValue + - secretsmanager:UpdateSecret + Resource: + - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/${env}/*' + + StellarKeyGenRolePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !Sub ${AWS::StackName}-stellar-keygen-policy + Roles: + - !Ref StellarKeyGenRole + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "logs:CreateLogGroup" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + Resource: + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" + + StellarSDKLayer: + Type: AWS::Lambda::LayerVersion + Properties: + LayerName: stellar-sdk-layer + Content: + S3Bucket: !Ref StellarLayerS3Bucket + S3Key: stellar-layer.zip + CompatibleRuntimes: + - nodejs18.x + + StellarKeyGenFunction: + Type: AWS::Lambda::Function + Properties: + Runtime: nodejs18.x + Handler: index.handler + Role: !GetAtt StellarKeyGenRole.Arn + Timeout: 30 + Layers: + - !Ref StellarSDKLayer + Code: + ZipFile: | + const { Keypair } = require('@stellar/stellar-sdk'); + const https = require('https'); + const url = require('url'); + + async function fundTestnetAccount(publicKey) { + return new Promise((resolve, reject) => { + https.get(`https://friendbot.stellar.org?addr=${publicKey}`, (resp) => { + let data = ''; + resp.on('data', (chunk) => data += chunk); + resp.on('end', () => { + console.log('Funding response:', data); + resolve(data); + }); + }).on('error', (err) => { + console.error('Error funding account:', err); + reject(err); + }); + }); + } + + function sendResponse(event, response) { + return new Promise((resolve, reject) => { + const parsedUrl = url.parse(event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + port: 443, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'Content-Type': '', + 'Content-Length': Buffer.byteLength(JSON.stringify(response)) + } + }; + + const request = https.request(requestOptions, (resp) => { + resolve(); + }); + + request.on('error', (error) => { + console.error('Error sending response:', error); + reject(error); + }); + + request.write(JSON.stringify(response)); + request.end(); + }); + } + + exports.handler = async (event, context) => { + try { + let response = { + Status: 'SUCCESS', + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + StackId: event.StackId, + PhysicalResourceId: context.logStreamName + }; + + if (event.RequestType === 'Delete') { + console.log('Delete request, sending success response'); + } else { + console.log('Generating new keypair'); + const pair = Keypair.random(); + response.Data = { + publicKey: pair.publicKey(), + secretKey: pair.secret() + }; + + if (event.ResourceProperties.KeyPairId === 'distribution') { + console.log('Funding distribution account on testnet'); + try { + await fundTestnetAccount(pair.publicKey()); + console.log('Successfully funded account'); + } catch (fundError) { + console.error('Error funding account:', fundError); + } + } + } + + await sendResponse(event, response); + return response; + + } catch (error) { + console.error('Error:', error); + const response = { + Status: 'FAILED', + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + StackId: event.StackId, + PhysicalResourceId: context.logStreamName, + Reason: error.toString() + }; + await sendResponse(event, response); + return response; + } + }; + + Sep10KeyGenPair: + Type: Custom::StellarKeyPair + Condition: GenerateSep10Keys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: sep10 + + DistributionKeyGenPair: + Type: Custom::StellarKeyPairx + Condition: GenerateDistributionKeys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: distribution + + ChannelKeyGenPair: + Type: Custom::StellarKeyPair + Condition: GenerateChannelKeys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: channel + + Sep10SigningPrivateKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SEP10_SIGNING_PRIVATE_KEY + Description: "SEP10 signing private key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.secretKey, !Ref Sep10SigningPrivateKey] + + SecretSep10SigningSeedSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SECRET_SEP10_SIGNING_SEED + Description: "SEP10 signing private key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.secretKey, !Ref Sep10SigningPrivateKey] + + Sep10SigningPublicKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SEP10_SIGNING_PUBLIC_KEY + Description: "SEP10 signing public key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.publicKey, !Ref Sep10SigningPublicKey] + + SecretSep10JwtSecretSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SECRET_SEP10_JWT_SECRET + Description: "SECRET_SEP10_JWT_SECRET" + SecretString: !Ref SecretSep10JwtSecret + + DistributionSeedSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/DISTRIBUTION_SEED + Description: "Distribution account seed" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionSeed] + + DistributionPublicKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/DISTRIBUTION_PUBLIC_KEY + Description: "Distribution account public key" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.publicKey, !Ref DistributionPublicKey] + + DistributionEncryptionPassphraseSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE + Description: "Distribution account encryption passphrase" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionAccountEncryptionPassphrase] + + ChannelEncryptionPassphraseSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE + Description: "Channel account encryption passphrase" + SecretString: !If [GenerateChannelKeys, !GetAtt ChannelKeyGenPair.secretKey, !Ref ChannelAccountEncryptionPassphrase] + + Ec256PrivateKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/EC256_PRIVATE_KEY + Description: "EC256 private key" + SecretString: !Ref Ec256PrivateKey + + Ec256PublicKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/EC256_PUBLIC_KEY + Description: "EC256 public key" + SecretString: !Ref Ec256PublicKey + + SecretSep24InteractiveUrlJwtSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET + Description: "SEP24 interactive URL JWT secret" + SecretString: !Ref SecretSep24InteractiveUrlJwtSecret + + SecretSep24MoreInfoUrlJwtSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SECRET_SEP24_MORE_INFO_URL_JWT_SECRET + Description: "SEP24 more info URL JWT secret" + SecretString: !Ref SecretSep24MoreInfoUrlJwtSecret + + Sep24JwtSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SEP24_JWT_SECRET + Description: "SEP24 JWT secret" + SecretString: !Ref Sep24JwtSecret + + SecretPlatformApiAuthSecretSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/SECRET_PLATFORM_API_AUTH_SECRET + SecretString: !Ref AnchorPlatformOutgoingJwtSecret + + AnchorPlatformOutgoingJwtSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/ANCHOR_PLATFORM_OUTGOING_JWT_SECRET + Description: "Anchor platform outgoing JWT secret" + SecretString: !Ref SecretPlatformApiAuthSecret + + AdminApiKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/ADMIN_API_KEY + Description: "Admin API key" + SecretString: !Ref AdminApiKey + + RecaptchaSiteKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/RECAPTCHA_SITE_KEY + Description: "Recaptcha site key" + SecretString: !Ref RecaptchaSiteKey + + RecaptchaSiteSecretKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/RECAPTCHA_SITE_SECRET_KEY + Description: "Recaptcha site secret key" + SecretString: !Ref RecaptchaSiteSecretKey + + AwsSnsSenderIdSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/AWS_SNS_SENDER_ID + Description: "Recaptcha site secret key" + SecretString: !Ref AwsSnsSenderId + + AwsSecretAccessKeySm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/AWS_SECRET_ACCESS_KEY + Description: "Recaptcha site secret key" + SecretString: !Ref AwsSecretAccessKey + + AwsAccessKeyIdSm: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/AWS_ACCESS_KEY_ID + Description: "Recaptcha site secret key" + SecretString: !Ref AwsAccessKeyId + +Outputs: + Sep10SigningPrivateKeySmArn: + Value: !Ref Sep10SigningPrivateKeySm + Export: + Name: !Sub ${AWS::StackName}-sep10-signing-private-key-sm-arn + Description: "ARN for SEP10_SIGNING_PRIVATE_KEY secret" + + Sep10SigningPublicKeySmArn: + Value: !Ref Sep10SigningPublicKeySm + Export: + Name: !Sub ${AWS::StackName}-sep10-signing-public-key-sm-arn + Description: "ARN for SEP10_SIGNING_PUBLIC_KEY secret" + + SecretSep10JwtSecretSmArn: + Value: !Ref SecretSep10JwtSecretSm + Export: + Name: !Sub ${AWS::StackName}-sep10-jwt-secret-key-sm-arn + Description: "ARN for SEP10_JWT_SECRET secret" + + DistributionSeedSmArn: + Value: !Ref DistributionSeedSm + Export: + Name: !Sub ${AWS::StackName}-distribution-seed-sm-arn + Description: "ARN for DISTRIBUTION_SEED secret" + + DistributionPublicKeySmArn: + Value: !Ref DistributionPublicKeySm + Export: + Name: !Sub ${AWS::StackName}-distribution-public-key-sm-arn + Description: "ARN for DISTRIBUTION_PUBLIC_KEY secret" + + DistributionEncryptionPassphraseSmArn: + Value: !Ref DistributionEncryptionPassphraseSm + Export: + Name: !Sub ${AWS::StackName}-distribution-encryption-passphrase-sm-arn + Description: "ARN for DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE secret" + + ChannelEncryptionPassphraseSmArn: + Value: !Ref ChannelEncryptionPassphraseSm + Export: + Name: !Sub ${AWS::StackName}-channel-encryption-passphrase-sm-arn + Description: "ARN for CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE secret" + + Ec256PrivateKeySmArn: + Value: !Ref Ec256PrivateKeySm + Export: + Name: !Sub ${AWS::StackName}-ec256-private-key-sm-arn + Description: "ARN for EC256_PRIVATE_KEY secret" + + Ec256PublicKeySmArn: + Value: !Ref Ec256PublicKeySm + Export: + Name: !Sub ${AWS::StackName}-ec256-public-key-sm-arn + Description: "ARN for EC256_PUBLIC_KEY secret" + + SecretSep24InteractiveUrlJwtSmArn: + Value: !Ref SecretSep24InteractiveUrlJwtSm + Export: + Name: !Sub ${AWS::StackName}-sep24-interactive-url-jwt-sm-arn + Description: "ARN for SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET secret" + + SecretSep24MoreInfoUrlJwtSmArn: + Value: !Ref SecretSep24MoreInfoUrlJwtSm + Export: + Name: !Sub ${AWS::StackName}-sep24-more-info-url-jwt-sm-arn + Description: "ARN for SECRET_SEP24_MORE_INFO_URL_JWT_SECRET secret" + + Sep24JwtSmArn: + Value: !Ref Sep24JwtSm + Export: + Name: !Sub ${AWS::StackName}-sep24-jwt-sm-arn + Description: "ARN for SEP24_JWT_SECRET secret" + + SecretPlatformApiAuthSecretArn: + Value: !Ref SecretPlatformApiAuthSecret + Export: + Name: !Sub ${AWS::StackName}-secret-platform-api-auth-secret-sm-arn + Description: "ARN for SECRET_PLATFORM_API_AUTH_SECRET " + + AnchorPlatformOutgoingJwtSmArn: + Value: !Ref AnchorPlatformOutgoingJwtSm + Export: + Name: !Sub ${AWS::StackName}-anchor-platform-outgoing-jwt-sm-arn + Description: "ARN for ANCHOR_PLATFORM_OUTGOING_JWT_SECRET secret" + + AdminApiKeySmArn: + Value: !Ref AdminApiKeySm + Export: + Name: !Sub ${AWS::StackName}-admin-api-key-sm-arn + Description: "ARN for ADMIN_API_KEY secret" + + RecaptchaSiteKeySmArn: + Value: !Ref RecaptchaSiteKeySm + Export: + Name: !Sub ${AWS::StackName}-recaptcha-site-key-sm-arn + Description: "ARN for RECAPTCHA_SITE_KEY secret" + + RecaptchaSiteSecretKeySmArn: + Value: !Ref RecaptchaSiteSecretKeySm + Export: + Name: !Sub ${AWS::StackName}-recaptcha-site-secret-key-sm-arn + Description: "ARN for RECAPTCHA_SITE_SECRET_KEY secret" diff --git a/resources/aws/cloudformation/sdp-keys.yaml b/resources/aws/cloudformation/sdp-keys.yaml new file mode 100644 index 000000000..28a14fbf0 --- /dev/null +++ b/resources/aws/cloudformation/sdp-keys.yaml @@ -0,0 +1,479 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Stack for managing Stellar and encryption keys in Secrets Manager' + +Parameters: + env: + Type: String + Default: "dev" + + namespace: + Type: String + Description: "Kubernetes only. namespace where SDP will be deployed" + Default: "sdp" + + DistributionAccountEncryptionPassphrase: + Type: String + Default: "" + NoEcho: true + + ChannelAccountEncryptionPassphrase: + Type: String + Default: "" + NoEcho: true + + SEP10SigningPrivateKey: + Type: String + Default: "" + NoEcho: true + + SEP10SigningPublicKey: + Type: String + Default: "" + + DistributionSeed: + Type: String + Default: "" + NoEcho: true + + DistributionPublicKey: + Type: String + Default: "" + + EC256PrivateKey: + Type: String + NoEcho: true + Default: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIPRRtyc5EQoNPFhkcDzC47B2Zpo5b0NiM3Ftvky86+bEoAoGCCqGSM49 + AwEHoUQDQgAEWinhVw0QHkZDeZ777zfBKT0cupULkpEd8Y52iPs76AT7JQ1cuGbm + jxJASNwp907KzNzOZJSV07bFdN/Tkwebgg== + -----END EC PRIVATE KEY----- + + EC256PublicKey: + Type: String + Default: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWinhVw0QHkZDeZ777zfBKT0cupUL + kpEd8Y52iPs76AT7JQ1cuGbmjxJASNwp907KzNzOZJSV07bFdN/Tkwebgg== + -----END PUBLIC KEY----- + +Conditions: + GenerateSep10Keys: !Equals [ !Ref SEP10SigningPrivateKey, "" ] + GenerateDistributionKeys: !Equals [ !Ref DistributionSeed, "" ] + GenerateChannelKeys: !Equals [ !Ref ChannelAccountEncryptionPassphrase, "" ] + +Resources: + StellarKeyGenRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: SecretsManagerAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - secretsmanager:CreateSecret + - secretsmanager:PutSecretValue + - secretsmanager:UpdateSecret + Resource: + - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/sdp/${env}/*' + + StellarKeyGenRolePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !Sub ${AWS::StackName}-stellar-keygen-policy + Roles: + - !Ref StellarKeyGenRole + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "logs:CreateLogGroup" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + Resource: + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" + + StellarSDKLayer: + Type: AWS::Lambda::LayerVersion + Properties: + LayerName: stellar-sdk-layer + Content: + S3Bucket: stellar-layer + S3Key: stellar-layer.zip + CompatibleRuntimes: + - nodejs18.x + + StellarKeyGenFunction: + Type: AWS::Lambda::Function + Properties: + Runtime: nodejs18.x + Handler: index.handler + Role: !GetAtt StellarKeyGenRole.Arn + Timeout: 30 + Layers: + - !Ref StellarSDKLayer + Code: + ZipFile: | + const { Keypair } = require('@stellar/stellar-sdk'); + const https = require('https'); + const url = require('url'); + + async function fundTestnetAccount(publicKey) { + return new Promise((resolve, reject) => { + https.get(`https://friendbot.stellar.org?addr=${publicKey}`, (resp) => { + let data = ''; + resp.on('data', (chunk) => data += chunk); + resp.on('end', () => { + console.log('Funding response:', data); + resolve(data); + }); + }).on('error', (err) => { + console.error('Error funding account:', err); + reject(err); + }); + }); + } + + function sendResponse(event, response) { + return new Promise((resolve, reject) => { + const parsedUrl = url.parse(event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + port: 443, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'Content-Type': '', + 'Content-Length': Buffer.byteLength(JSON.stringify(response)) + } + }; + + const request = https.request(requestOptions, (resp) => { + resolve(); + }); + + request.on('error', (error) => { + console.error('Error sending response:', error); + reject(error); + }); + + request.write(JSON.stringify(response)); + request.end(); + }); + } + + exports.handler = async (event, context) => { + try { + let response = { + Status: 'SUCCESS', + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + StackId: event.StackId, + PhysicalResourceId: context.logStreamName + }; + + if (event.RequestType === 'Delete') { + console.log('Delete request, sending success response'); + } else { + console.log('Generating new keypair'); + const pair = Keypair.random(); + response.Data = { + publicKey: pair.publicKey(), + secretKey: pair.secret() + }; + + if (event.ResourceProperties.KeyPairId === 'distribution') { + console.log('Funding distribution account on testnet'); + try { + await fundTestnetAccount(pair.publicKey()); + console.log('Successfully funded account'); + } catch (fundError) { + console.error('Error funding account:', fundError); + } + } + } + + await sendResponse(event, response); + return response; + + } catch (error) { + console.error('Error:', error); + const response = { + Status: 'FAILED', + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + StackId: event.StackId, + PhysicalResourceId: context.logStreamName, + Reason: error.toString() + }; + await sendResponse(event, response); + return response; + } + }; + + Sep10KeyGenPair: + Type: Custom::StellarKeyPair + Condition: GenerateSep10Keys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: sep10 + + DistributionKeyGenPair: + Type: Custom::StellarKeyPair + Condition: GenerateDistributionKeys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: distribution + + ChannelKeyGenPair: + Type: Custom::StellarKeyPair + Condition: GenerateChannelKeys + Properties: + ServiceToken: !GetAtt StellarKeyGenFunction.Arn + KeyPairId: channel + + Sep10Secret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/sep10 + Description: "SEP10 signing keys" + SecretString: !Join + - '' + - - '{"signing_public_key":"' + - !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.publicKey, !Ref SEP10SigningPublicKey] + - '","signing_private_key":"' + - !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.secretKey, !Ref SEP10SigningPrivateKey] + - '"}' + + DistributionSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/distribution + Description: "Distribution account keys and passphrase" + SecretString: !Join + - '' + - - '{"seed":"' + - !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionSeed] + - '","public_key":"' + - !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.publicKey, !Ref DistributionPublicKey] + - '","encryption_passphrase":"' + - !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionAccountEncryptionPassphrase] + - '"}' + + ChannelSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/channel + Description: "Channel account encryption passphrase" + SecretString: !Join + - '' + - - '{"encryption_passphrase":"' + - !If [GenerateChannelKeys, !GetAtt ChannelKeyGenPair.secretKey, !Ref ChannelAccountEncryptionPassphrase] + - '"}' + + Ec256Secret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/ec256 + Description: "EC256 key pair" + SecretString: !Join + - '' + - - '{"private_key":"' + - !Ref EC256PrivateKey + - '","public_key":"' + - !Ref EC256PublicKey + - '"}' + # below this line is secrets for EKS. TODO: consolidate + + Ec256PrivateKeySecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/EC256_PRIVATE_KEY + Description: "EC256 private key" + SecretString: !Ref EC256PrivateKey + + Ec256PublicKeySecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/EC256_PUBLIC_KEY + Description: "EC256 public key" + SecretString: !Ref EC256PublicKey + + Sep10SigningPrivateKeySecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SEP10_SIGNING_PRIVATE_KEY + Description: "SEP10 signing private key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.secretKey, !Ref SEP10SigningPrivateKey] + + SecretSep10SigningSeed: + Type: AWS::SecretsManager::Secret + DependsOn: Sep10SigningPrivateKeySecret + Properties: + Name: !Sub /${namespace}/SECRET_SEP10_SIGNING_SEED + Description: "SEP10 signing private key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.secretKey, !Ref SEP10SigningPrivateKey] + + Sep10SigningPublicKeySecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SEP10_SIGNING_PUBLIC_KEY + Description: "SEP10 signing public key" + SecretString: !If [GenerateSep10Keys, !GetAtt Sep10KeyGenPair.publicKey, !Ref SEP10SigningPublicKey] + + DistributionSeedSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/DISTRIBUTION_SEED + Description: "Distribution account seed" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionSeed] + + DistributionPublicKeySecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/DISTRIBUTION_PUBLIC_KEY + Description: "Distribution account public key" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.publicKey, !Ref DistributionPublicKey] + + DistributionEncryptionPassphraseSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE + Description: "Distribution account encryption passphrase" + SecretString: !If [GenerateDistributionKeys, !GetAtt DistributionKeyGenPair.secretKey, !Ref DistributionAccountEncryptionPassphrase] + + ChannelEncryptionPassphraseSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE + Description: "Channel account encryption passphrase" + SecretString: !If [GenerateChannelKeys, !GetAtt ChannelKeyGenPair.secretKey, !Ref ChannelAccountEncryptionPassphrase] + + SecretPlatformApiAuthSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SECRET_PLATFORM_API_AUTH_SECRET + SecretString: "mySdpToAnchorPlatformSecret" + + SecretSep10JwtSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SECRET_SEP10_JWT_SECRET + SecretString: "jwt_secret_1234567890" + + SecretSep24InteractiveUrlJwtSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SECRET_SEP24_INTERACTIVE_URL_JWT_SECRET + SecretString: "jwt_secret_1234567890" + + SecretSep24MoreInfoUrlJwtSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SECRET_SEP24_MORE_INFO_URL_JWT_SECRET + Description: "Distribution account encryption passphrase" + SecretString: "jwt_secret_1234567890" + + Sep24JwtSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/SEP24_JWT_SECRET + SecretString: "jwt_secret_1234567890" + + AnchorPlatformOutgoingJwtSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /${namespace}/ANCHOR_PLATFORM_OUTGOING_JWT_SECRET + SecretString: "jwt_secret_1234567890" + + RecaptchaSiteSecretKey: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/${env}/recaptcha-site-secret-key + Description: "Recaptcha site secret key" + SecretString: "6Lcw864qAAAAAJCtS-7NSSbu-iRX2ZS8iu4xUGIc" + + AdminApiKey: + Type: AWS::SecretsManager::Secret + Properties: + Name: !Sub /sdp/ADMIN_API_KEY + SecretString: "admin-api-key" + +Outputs: + Sep10SecretArn: + Value: !Ref Sep10Secret + Export: + Name: !Sub ${AWS::StackName}-sep10-secret-arn + + DistributionSecretArn: + Value: !Ref DistributionSecret + Export: + Name: !Sub ${AWS::StackName}-distribution-secret-arn + + ChannelSecretArn: + Value: !Ref ChannelSecret + Export: + Name: !Sub ${AWS::StackName}-channel-secret-arn + + Ec256SecretArn: + Value: !Ref Ec256Secret + Export: + Name: !Sub ${AWS::StackName}-ec256-secret-arn + + Sep10SigningPrivateKeySecretArn: + Value: !Ref Sep10SigningPrivateKeySecret + Export: + Name: !Sub ${AWS::StackName}-sep10-signing-private-key-secret-arn + + Sep10SigningPublicKeySecretArn: + Value: !Ref Sep10SigningPublicKeySecret + Export: + Name: !Sub ${AWS::StackName}-sep10-signing-public-key-secret-arn + + DistributionSeedSecretArn: + Value: !Ref DistributionSeedSecret + Export: + Name: !Sub ${AWS::StackName}-distribution-seed-secret-arn + + DistributionPublicKeySecretArn: + Value: !Ref DistributionPublicKeySecret + Export: + Name: !Sub ${AWS::StackName}-distribution-public-key-secret-arn + + DistributionEncryptionPassphraseSecretArn: + Value: !Ref DistributionEncryptionPassphraseSecret + Export: + Name: !Sub ${AWS::StackName}-distribution-encryption-passphrase-secret-arn + + ChannelEncryptionPassphraseSecretArn: + Value: !Ref ChannelEncryptionPassphraseSecret + Export: + Name: !Sub ${AWS::StackName}-channel-encryption-passphrase-secret-arn + + Ec256PrivateKeySecretArn: + Value: !Ref Ec256PrivateKeySecret + Export: + Name: !Sub ${AWS::StackName}-ec256-private-key-secret-arn + + Ec256PublicKeySecretArn: + Value: !Ref Ec256PublicKeySecret + Export: + Name: !Sub ${AWS::StackName}-ec256-public-key-secret-arn + + RecaptchaSiteSecretKeyArn: + Value: !Ref RecaptchaSiteSecretKey + Export: + Name: !Sub ${AWS::StackName}-recaptcha-site-secret-key-arn + diff --git a/resources/aws/cloudformation/sdp-network-eks.yaml b/resources/aws/cloudformation/sdp-network-eks.yaml new file mode 100644 index 000000000..cc8504471 --- /dev/null +++ b/resources/aws/cloudformation/sdp-network-eks.yaml @@ -0,0 +1,297 @@ +Parameters: + AWSRegion: + Type: String + Default: "us-west-1" + + env: + Type: String + Default: "dev" + AllowedValues: + - dev + - staging + - prod + + ExistingVPCId: + Type: String + Default: "" + Description: "If specified, use an existing VPC instead of creating a new one" + + ExistingPublicSubnet1Id: + Type: String + Default: "" + Description: "If using existing VPC, specify the first public subnet ID" + + ExistingPublicSubnet2Id: + Type: String + Default: "" + Description: "If using existing VPC, specify the second public subnet ID" + + ExistingPrivateSubnet1Id: + Type: String + Default: "" + Description: "If using existing VPC, specify the first private subnet ID" + + ExistingPrivateSubnet2Id: + Type: String + Default: "" + Description: "If using existing VPC, specify the second private subnet ID" + + VPCCidr: + Type: String + Default: "10.0.0.0/16" + + PublicSubnet1CIDR: + Type: String + Default: "10.0.0.0/24" + + PublicSubnet2CIDR: + Type: String + Default: "10.0.1.0/24" + + PrivateSubnet1CIDR: + Type: String + Default: "10.0.2.0/24" + + PrivateSubnet2CIDR: + Type: String + Default: "10.0.3.0/24" + +Conditions: + CreateNewVPC: !Equals + - !Ref ExistingVPCId + - "" + +Resources: + SDPVPC: + Type: AWS::EC2::VPC + Condition: CreateNewVPC + Properties: + CidrBlock: !Ref VPCCidr + EnableDnsHostnames: true + EnableDnsSupport: true + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-vpc + - Key: env + Value: !Ref env + + InternetGateway: + Type: AWS::EC2::InternetGateway + Condition: CreateNewVPC + Properties: + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-igw + - Key: env + Value: !Ref env + + AttachGateway: + Type: AWS::EC2::VPCGatewayAttachment + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + InternetGatewayId: !Ref InternetGateway + + # Public Subnets + PublicSubnet1: + Type: AWS::EC2::Subnet + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + CidrBlock: !Ref PublicSubnet1CIDR + AvailabilityZone: !Sub ${AWS::Region}a + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-public-subnet-1 + - Key: env + Value: !Ref env + + PublicSubnet2: + Type: AWS::EC2::Subnet + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + CidrBlock: !Ref PublicSubnet2CIDR + AvailabilityZone: !Sub ${AWS::Region}c + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-public-subnet-2 + - Key: env + Value: !Ref env + + # Private Subnets + PrivateSubnet1: + Type: AWS::EC2::Subnet + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + CidrBlock: !Ref PrivateSubnet1CIDR + AvailabilityZone: !Sub ${AWS::Region}a + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-private-subnet-1 + - Key: env + Value: !Ref env + + PrivateSubnet2: + Type: AWS::EC2::Subnet + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + CidrBlock: !Ref PrivateSubnet2CIDR + AvailabilityZone: !Sub ${AWS::Region}c + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-private-subnet-2 + - Key: env + Value: !Ref env + + # NAT Gateway + NatGatewayEIP: + Type: AWS::EC2::EIP + Condition: CreateNewVPC + DependsOn: AttachGateway + Properties: + Domain: vpc + Tags: + - Key: env + Value: !Ref env + + NatGateway: + Type: AWS::EC2::NatGateway + Condition: CreateNewVPC + Properties: + AllocationId: !GetAtt NatGatewayEIP.AllocationId + SubnetId: !Ref PublicSubnet1 + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-nat-gateway + - Key: env + Value: !Ref env + + # Route Tables + PublicRouteTable: + Type: AWS::EC2::RouteTable + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-public-route-table + - Key: env + Value: !Ref env + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Condition: CreateNewVPC + Properties: + VpcId: !Ref SDPVPC + Tags: + - Key: Name + Value: !Sub ${env}-${AWS::StackName}-private-route-table + - Key: env + Value: !Ref env + + PublicRoute: + Type: AWS::EC2::Route + Condition: CreateNewVPC + DependsOn: AttachGateway + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PrivateRoute: + Type: AWS::EC2::Route + Condition: CreateNewVPC + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway + + # Subnet Route Table Associations + PublicSubnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNewVPC + Properties: + SubnetId: !Ref PublicSubnet1 + RouteTableId: !Ref PublicRouteTable + + PublicSubnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNewVPC + Properties: + SubnetId: !Ref PublicSubnet2 + RouteTableId: !Ref PublicRouteTable + + PrivateSubnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNewVPC + Properties: + SubnetId: !Ref PrivateSubnet1 + RouteTableId: !Ref PrivateRouteTable + + PrivateSubnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateNewVPC + Properties: + SubnetId: !Ref PrivateSubnet2 + RouteTableId: !Ref PrivateRouteTable + +Outputs: + VPCId: + Description: VPC ID + Value: !If + - CreateNewVPC + - !Ref SDPVPC + - !Ref ExistingVPCId + Export: + Name: !Sub ${AWS::StackName}-vpc-id + + VPCCidr: + Description: VIPC CIDR + Value: !If + - CreateNewVPC + - !Ref VPCCidr + - !GetAtt SDPVPC.CidrBlock + Export: + Name: !Sub ${AWS::StackName}-vpc-cidr + + + PublicSubnet1: + Description: Public Subnet 1 ID + Value: !If + - CreateNewVPC + - !Ref PublicSubnet1 + - !Ref ExistingPublicSubnet1Id + Export: + Name: !Sub ${AWS::StackName}-public-subnet-1 + + PublicSubnet2: + Description: Public Subnet 2 ID + Value: !If + - CreateNewVPC + - !Ref PublicSubnet2 + - !Ref ExistingPublicSubnet2Id + Export: + Name: !Sub ${AWS::StackName}-public-subnet-2 + + PrivateSubnet1: + Description: Private Subnet 1 ID + Value: !If + - CreateNewVPC + - !Ref PrivateSubnet1 + - !Ref ExistingPrivateSubnet1Id + Export: + Name: !Sub ${AWS::StackName}-private-subnet-1 + + PrivateSubnet2: + Description: Private Subnet 2 ID + Value: !If + - CreateNewVPC + - !Ref PrivateSubnet2 + - !Ref ExistingPrivateSubnet2Id + Export: + Name: !Sub ${AWS::StackName}-private-subnet-2 \ No newline at end of file