Skip to content

Commit

Permalink
feat(irsa): add irsa configuration, add service account to give pods …
Browse files Browse the repository at this point in the history
…access to db secret
  • Loading branch information
briancaffey committed Jun 6, 2021
1 parent 0c6c811 commit 55376d3
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 16 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ destroy-eks:
diff-eks:
cdk diff --app='./lib/integ.django-eks.js';

## watch
watch:
npm run watch

## run all tests for projen project
test:
npm run test
Expand Down
6 changes: 1 addition & 5 deletions src/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export class RdsPostgresInstance extends cdk.Construct {

public rdsPostgresInstance: rds.IDatabaseInstance;
public rdsSecurityGroup: ec2.ISecurityGroup;
// public databaseUsername:

constructor(scope: cdk.Construct, id: string, props: RdsPostgresInstanceProps) {
super(scope, id);
Expand All @@ -35,10 +34,7 @@ export class RdsPostgresInstance extends cdk.Construct {
vpcPlacement: { subnetType: ec2.SubnetType.ISOLATED },
port: 5432,
securityGroups: [rdsSecurityGroup],
credentials: {
username: 'postgres',
password: props.secret.secretValue,
},
credentials: rds.Credentials.fromSecret(props.secret),
});
this.rdsPostgresInstance = rdsPostgresInstance;
}
Expand Down
166 changes: 159 additions & 7 deletions src/django-eks.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecrAssets from '@aws-cdk/aws-ecr-assets';
import * as eks from '@aws-cdk/aws-eks';
// import * as ecrAssets from '@aws-cdk/aws-ecr-assets';
// import * as logs from '@aws-cdk/aws-logs';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
import * as cdk from '@aws-cdk/core';
import { CfnJson } from '@aws-cdk/core';
import { RdsPostgresInstance } from './database';
// import { ElastiCacheCluster } from './elasticache';

// k8s manifests
import { appIngress } from './ingress';
import { nginxDeployment, nginxService } from './nginx';
// import { nginxDeployment, nginxService } from './nginx';
import { MigrateJob } from './eks/resources/migrate';
// import { WebResources } from './eks/resources/web';
// import { appIngress } from './ingress';
import { ApplicationVpc } from './vpc';


Expand Down Expand Up @@ -110,6 +113,9 @@ export class DjangoEks extends cdk.Construct {
defaultCapacity: 2,
});

/**
* Namespace for application
*/
this.cluster.addManifest('app-namespace', {
apiVersion: 'v1',
kind: 'Namespace',
Expand All @@ -118,14 +124,73 @@ export class DjangoEks extends cdk.Construct {
},
});

const DB_SECRET_NAME = 'dbSecret';
/**
* Secret used for RDS postgres password
*/
this.secret = new secretsmanager.Secret(scope, 'dbSecret', {
secretName: 'dbSecret',
secretName: DB_SECRET_NAME,
description: 'secret for rds',
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: 'postgres' }),
generateStringKey: 'password',
excludePunctuation: true,
includeSpace: false,
},
});


const POD_SERVICE_ACCOUNT_NAME = 'pod-service-account';
const oidcProviderId = this.cluster.openIdConnectProvider.openIdConnectProviderIssuer;

/**
* The Principal that will assume the podRole
*/
const federatedPrincipal = new iam.FederatedPrincipal(
this.cluster.openIdConnectProvider.openIdConnectProviderArn,
{
StringEquals: new CfnJson(scope, "FederatedPrincipalCondition", {
value: {
[`${oidcProviderId}:aud`]: "sts.amazonaws.com",
[`${oidcProviderId}:sub`]: `system:serviceaccount:app:${POD_SERVICE_ACCOUNT_NAME}`
}
})
}, "sts:AssumeRoleWithWebIdentity"
);

/**
* Setup a new IAM Role that will be used by Pods to access the secret and S3 bucket
*
* This role is assumed by the Principal defined above
*/
const podRole = new iam.Role(scope, 'podRole', {
assumedBy: federatedPrincipal,
});

const podServiceAccount = {
apiVersion: 'v1',
kind: 'ServiceAccount',
metadata: {
name: POD_SERVICE_ACCOUNT_NAME,
namespace: 'app',
annotations: {
'eks.amazonaws.com/role-arn': podRole.roleArn,
},
},
};

/**
* Give the podRole read access to the bucket and read/write access to the S3 bucket
*/
this.secret.grantRead(podRole);
this.staticFileBucket.grantReadWrite(podRole);

/**
* Create a service account manfiest that will be used by pods
* The ARN will be passed into the templates
*/
this.cluster.addManifest('pod-service-account', podServiceAccount);

/**
* RDS instance
*/
Expand All @@ -146,6 +211,7 @@ export class DjangoEks extends cdk.Construct {
* Here the appSecurityGroup created above is added to that ASG
*/
this.cluster.defaultCapacity?.addSecurityGroup(appSecurityGroup);
// this.secret.grantRead(this.cluster.def);

/**
* Allow th ASG to accesss the database
Expand Down Expand Up @@ -211,8 +277,94 @@ export class DjangoEks extends cdk.Construct {
});

// sample nginx deployment and service for demonstration
this.cluster.addManifest('nginx-deployment', nginxDeployment);
this.cluster.addManifest('nginx-service', nginxService);
this.cluster.addManifest('app-ingresss', appIngress);
// this.cluster.addManifest('nginx-deployment', nginxDeployment);
// this.cluster.addManifest('nginx-service', nginxService);

// backend docker image

const backendImage = new ecrAssets.DockerImageAsset(scope, 'backendImage', {
directory: props.imageDirectory,
});

// TODO: use https://github.com/aws/aws-secretsmanager-caching-python/blob/master/test/integ/test_aws_secretsmanager_caching.py
// instead of passing password string to environment variable

/**
* Common environment variables for Jobs and Deployments
*/
const env = [
{
name: 'DEBUG',
value: '0',
},
{
// this is used in the application to fetch the secret value
name: 'DB_SECRET_NAME',
value: DB_SECRET_NAME,

},
{
name: 'POSTGRES_SERVICE_HOST',
value: database.rdsPostgresInstance.dbInstanceEndpointAddress,
},
{
name: 'DJANGO_SETTINGS_MODULE',
value: 'backend.settings.production',
},
];

// Django K8s resources

const migrateJob = new MigrateJob(scope, 'django-migrate-job', {
cluster: this.cluster,
backendImage,
namespace: 'app',
env,
});

/**
* Job that runs Django migrations
*/
this.cluster.addManifest('migrate-job', migrateJob.manifest);

// web service and deployment
// const webResources = new WebResources(scope, 'web-resources', {
// env,
// cluster: this.cluster,
// webCommand: props.webCommand ?? ['./scripts/start_prod.sh'],
// backendImage,
// namespace: 'app',
// });

/**
* Add deployment and service manifests for web to the cluster
*/
// this.cluster.addManifest('app-ingresss', appIngress);
// this.cluster.addManifest('web-deployment', webResources.deploymentManifest);
// this.cluster.addManifest('web-service', webResources.serviceManifest);


/**
* Get the ALB address using KubernetesObjectValue as a String
*/

// const albAddress = new eks.KubernetesObjectValue(scope, 'AlbAddress', {
// cluster: this.cluster,
// objectType: 'ingress',
// objectNamespace: 'app',
// objectName: 'app-ingress',
// jsonPath: '.items[0].status.loadBalancer.ingress[0].hostname',
// });

/**
* Route53 A Record pointing to ALB that is created by AWS Application Load Balancer Controller
*/


/**
* Output the Load Balancer URL as a CfnOutput
*/
// new cdk.CfnOutput(this, 'apiUrl', { value: albAddress.value });

}
}
47 changes: 47 additions & 0 deletions src/eks/resources/migrate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as ecrAssets from '@aws-cdk/aws-ecr-assets';
import * as eks from '@aws-cdk/aws-eks';
import * as cdk from '@aws-cdk/core';

interface MigrateJobProps {
env: {[key: string]: string}[];
cluster: eks.ICluster;
backendImage: ecrAssets.DockerImageAsset;
namespace: string;
}

export class MigrateJob extends cdk.Construct {
public manifest: any;
constructor(scope: cdk.Construct, id: string, props: MigrateJobProps) {
super(scope, id);

const migrateJobManifest: any = {
apiVersion: 'batch/v1',
kind: 'Job',
metadata: {
namespace: props.namespace,
name: 'django-migrate',
},
spec: {
template: {
spec: {
serviceAccountName: 'pod-service-account',
containers: [
{
name: 'migrate',
image: props.backendImage.imageUri,
env: props.env,
args: ['python3', 'manage.py', 'migrate'],
},
],
restartPolicy: 'Never',
},
},
},
};

this.manifest = migrateJobManifest;

// props.cluster.addManifest('migrate-job', migrateJobManifest);

}
}
92 changes: 92 additions & 0 deletions src/eks/resources/web/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as ecrAssets from '@aws-cdk/aws-ecr-assets';
import * as eks from '@aws-cdk/aws-eks';
import * as cdk from '@aws-cdk/core';

interface WebResourcesProps {
env: {[key: string]: string}[];
cluster: eks.ICluster;
backendImage: ecrAssets.DockerImageAsset;
namespace: string;
webCommand: string[];
}

/**
* This construct provides the Kubernetes manifests for the Django web application
*
* It includes a service and a deployment manifest that share some common names and labels
*
* The service is references by the Ingress object called `app-ingress`
*
*/
export class WebResources extends cdk.Construct {

public serviceManifest: any;
public deploymentManifest: any;

constructor(scope: cdk.Construct, id: string, props: WebResourcesProps) {
super(scope, id);

const name = 'api-http';
const selector = { app: name };

const webDeploymentManifest: any = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
namespace: props.namespace,
name,
},
spec: {
replicas: 1,
selector: {
matchLabels: selector,
},
template: {
metadata: {
labels: selector,
},
spec: {
containers: [
{
name,
image: props.backendImage.imageUri,
env: props.env,
args: props.webCommand,
ports: [
{
containerPort: 8000,
name: 'http',
},
],
},
],
restartPolicy: 'Always',
},
},
},
};

const webServiceManifest: any = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
labels: selector,
name,
namespace: props.namespace,
},
spec: {
selector,
ports: [
{
port: 80,
targetPort: 8000,
protocol: 'TCP',
},
],
},
};

this.deploymentManifest = webDeploymentManifest;
this.serviceManifest = webServiceManifest;
}
}
Loading

0 comments on commit 55376d3

Please sign in to comment.