mirror of
https://gitea.com/mcereda/oam.git
synced 2026-02-09 05:44:23 +00:00
chore(aws/ecs): populate environment variables from secret manager secrets
This commit is contained in:
@@ -62,6 +62,9 @@ By default, containers behave like other Linux processes with respect to access
|
||||
Unless explicitly protected and guaranteed, all containers running on the same host share CPU, memory, and other
|
||||
resources much like normal processes running on that host share those very same resources.
|
||||
|
||||
Specify the _execution role_ to allow ECS components to call AWS services when starting tasks.<br/>
|
||||
Specify the _task role_ to allow the app running in a task to call AWS services.
|
||||
|
||||
<details>
|
||||
<summary>Usage</summary>
|
||||
|
||||
@@ -172,6 +175,57 @@ Whatever the [launch type] or [capacity provider][capacity providers]:
|
||||
> [!important]
|
||||
> Task definition's parameters differ depending on the launch type.
|
||||
|
||||
Specifying the _Execution Role_ in a task definition grants that IAM Role's permissions **to the ECS container
|
||||
agent**, allowing it to call AWS when starting tasks.<br/>
|
||||
This is required when ECS (and **not** the app in the task's container) needs to make calls to, i.e., read a value from
|
||||
Secrets Manager.<br/>
|
||||
This IAM Role must allow `ecs.amazonaws.com` to assume it.
|
||||
|
||||
<details style='padding: 0 0 1rem 1rem'>
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowECSToAssumeThisVeryRole",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "ecs.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Specifying the _Task Role_ in a task definition grants that IAM Role's permissions **to the task's container**.<br/>
|
||||
This is required when the app in the task's container (and **not** ECS) needs to make calls to, i.e., recover a file
|
||||
from S3.<br/>
|
||||
This IAM Role must allow `ecs-tasks.amazonaws.com` to assume it.
|
||||
|
||||
<details style='padding: 0 0 1rem 1rem'>
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowECSTasksToAssumeThisVeryRole",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "ecs-tasks.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Standalone tasks
|
||||
|
||||
Refer [Amazon ECS standalone tasks].
|
||||
@@ -1527,6 +1581,8 @@ Options:
|
||||
|
||||
- [Pass Secrets Manager secrets through Amazon ECS environment variables].
|
||||
|
||||
Use Secrets Manager in environment variables
|
||||
|
||||
When setting environment variables to secrets from Secrets Manager, it is the **execution** role (and **not** the task
|
||||
role) that must have the permissions required to access them.
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ When replicating a secret, Secrets Manager creates a copy of the original (A.K.A
|
||||
as a _replica_ secret.<br/>
|
||||
The replica secret remains linked to the primary secret, and is updated when a new version of the primary is created.
|
||||
|
||||
Secrets Manager uses [IAM] to allow only authorized users to access or modify a secret.
|
||||
Secrets Manager uses [IAM] to allow only authorized users to access or modify a secret.<br/>
|
||||
Permissions for them can be set in IAM Policies that are _identity-based_ (the usual ones, granted to IAM Identities),
|
||||
or _resource-based_ (secret-specific).
|
||||
|
||||
_Managed_ secrets are created and managed by the AWS service that created them.<br/>
|
||||
The managing service might also restrict users from updating secrets, or deleting them without a recovery period.<br/>
|
||||
@@ -64,6 +66,8 @@ Managed secrets use a naming convention that includes the ID of the service mana
|
||||
|
||||
### Sources
|
||||
|
||||
- [Authentication and access control for AWS Secrets Manager]
|
||||
|
||||
<!--
|
||||
Reference
|
||||
═╬═Time══
|
||||
@@ -76,5 +80,7 @@ Managed secrets use a naming convention that includes the ID of the service mana
|
||||
[Secrets management]: ../../secrets%20management.md
|
||||
|
||||
<!-- Upstream -->
|
||||
[Authentication and access control for AWS Secrets Manager]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access.html
|
||||
|
||||
<!-- Others -->
|
||||
[JSON structure of AWS Secrets Manager secrets]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_secret_json_structure.html
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
Output as PulumiOutput,
|
||||
interpolate as pulumiInterpolate, jsonStringify as pulumiJsonStringify,
|
||||
} from "@pulumi/pulumi";
|
||||
import { Secret, SecretPolicy, SecretVersion } from "@pulumi/aws/secretsmanager";
|
||||
import {
|
||||
GetRoleResult,
|
||||
getRoleOutput,
|
||||
} from "@pulumi/aws/iam";
|
||||
import {
|
||||
GetClusterResult, Service, TaskDefinition,
|
||||
getClusterOutput,
|
||||
} from "@pulumi/aws/ecs";
|
||||
import {
|
||||
GetSecurityGroupResult, GetSubnetResult,
|
||||
getSecurityGroupOutput, getSubnetOutput,
|
||||
} from "@pulumi/aws/ec2";
|
||||
|
||||
const executionRole: PulumiOutput<GetRoleResult> = getRoleOutput({ name: "ecsExecutionRole" });
|
||||
|
||||
const composite_secret: Secret = new Secret(
|
||||
"smSecretsInEnv-composite",
|
||||
{
|
||||
name: "composite-secret",
|
||||
description: "Some value-only secret",
|
||||
tags: {},
|
||||
},
|
||||
);
|
||||
new SecretVersion(
|
||||
"smSecretsInEnv-composite",
|
||||
{
|
||||
secretId: composite_secret.id,
|
||||
secretString: pulumiJsonStringify({
|
||||
someField: "someValue",
|
||||
}),
|
||||
versionStages: [
|
||||
"AWSCURRENT",
|
||||
],
|
||||
},
|
||||
{ parent: composite_secret },
|
||||
);
|
||||
new SecretPolicy(
|
||||
"smSecretsInEnv-composite",
|
||||
{
|
||||
secretArn: composite_secret.arn,
|
||||
policy: pulumiJsonStringify({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: {
|
||||
AWS: executionRole.arn,
|
||||
},
|
||||
Action: "secretsmanager:GetSecretValue",
|
||||
Resource: composite_secret.arn,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{ parent: composite_secret },
|
||||
);
|
||||
|
||||
const valueOnly_secret: Secret = new Secret(
|
||||
"smSecretsInEnv-valueOnly",
|
||||
{
|
||||
name: "valueOnly-secret",
|
||||
description: "Some value-only secret",
|
||||
tags: {},
|
||||
},
|
||||
);
|
||||
new SecretVersion(
|
||||
"smSecretsInEnv-valueOnly",
|
||||
{
|
||||
secretId: valueOnly_secret.id,
|
||||
secretString: "value-only secret",
|
||||
versionStages: [
|
||||
"AWSCURRENT",
|
||||
],
|
||||
},
|
||||
{ parent: valueOnly_secret },
|
||||
);
|
||||
new SecretPolicy(
|
||||
"smSecretsInEnv-valueOnly",
|
||||
{
|
||||
secretArn: valueOnly_secret.arn,
|
||||
policy: pulumiJsonStringify({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: {
|
||||
AWS: executionRole.arn,
|
||||
},
|
||||
Action: "secretsmanager:GetSecretValue",
|
||||
Resource: valueOnly_secret.arn,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{ parent: valueOnly_secret },
|
||||
);
|
||||
|
||||
const containerDefinitions = [
|
||||
{
|
||||
name: "echo-server",
|
||||
image: "mendhak/http-https-echo:38@sha256:c73e039e883944a38e37eaba829eb9a67641cd03eff868827683951feceef96e",
|
||||
essential: true,
|
||||
|
||||
environment: [
|
||||
{
|
||||
name: "WHATEVER",
|
||||
value: "whatever",
|
||||
},
|
||||
],
|
||||
secrets: [
|
||||
// loaded as environment variables, but their value is taken from Secrets Manager
|
||||
{
|
||||
name: "VALUE_ONLY_SECRET",
|
||||
valueFrom: valueOnly_secret.arn,
|
||||
},
|
||||
{
|
||||
// requires specifying the field name, prefixed by ':' and followed by '::'
|
||||
name: "COMPOSITE_SECRET",
|
||||
valueFrom: pulumiInterpolate`${composite_secret.arn}:someField::`,
|
||||
},
|
||||
],
|
||||
|
||||
healthCheck: {
|
||||
command: [
|
||||
"CMD-SHELL",
|
||||
`nc -vz -w1 localhost 8080 || nc -vz -w1 localhost 8443`,
|
||||
],
|
||||
interval: 6,
|
||||
retries: 3,
|
||||
startPeriod: 3,
|
||||
timeout: 5,
|
||||
},
|
||||
portMappings: [
|
||||
{
|
||||
name: "http",
|
||||
protocol: "tcp",
|
||||
appProtocol: "http",
|
||||
containerPort: 8080,
|
||||
hostPort: 8080,
|
||||
},
|
||||
{
|
||||
name: "https",
|
||||
protocol: "tcp",
|
||||
appProtocol: "http",
|
||||
containerPort: 8443,
|
||||
hostPort: 8443,
|
||||
},
|
||||
],
|
||||
mountPoints: [],
|
||||
systemControls: [],
|
||||
volumesFrom: [],
|
||||
},
|
||||
];
|
||||
const taskDefinition = new TaskDefinition(
|
||||
"smSecretsInEnv",
|
||||
{
|
||||
family: "SmSecretsInEnv",
|
||||
tags: {},
|
||||
|
||||
containerDefinitions: pulumiJsonStringify(containerDefinitions),
|
||||
cpu: "256",
|
||||
memory: "512",
|
||||
executionRoleArn: executionRole.arn,
|
||||
networkMode: "awsvpc",
|
||||
requiresCompatibilities: [
|
||||
"FARGATE",
|
||||
],
|
||||
runtimePlatform: {
|
||||
cpuArchitecture: "X86_64",
|
||||
operatingSystemFamily: "LINUX",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const cluster: PulumiOutput<GetClusterResult> = getClusterOutput({ clusterName: "dev" });
|
||||
const securityGroup: PulumiOutput<GetSecurityGroupResult> = getSecurityGroupOutput({
|
||||
vpcId: "vpc-01234567",
|
||||
name: "default",
|
||||
});
|
||||
const subnet: PulumiOutput<GetSubnetResult> = getSubnetOutput({
|
||||
availabilityZone: "eu-west-1a",
|
||||
state: "available",
|
||||
filters: [{
|
||||
name: "tag:Name",
|
||||
values: [
|
||||
"priv-*",
|
||||
],
|
||||
}],
|
||||
});
|
||||
new Service(
|
||||
"smSecretsInEnv",
|
||||
{
|
||||
name: "SmSecretsInEnv",
|
||||
cluster: cluster.arn,
|
||||
taskDefinition: taskDefinition.arn,
|
||||
tags: {},
|
||||
|
||||
desiredCount: 1,
|
||||
launchType: "FARGATE",
|
||||
networkConfiguration: {
|
||||
subnets: [
|
||||
subnet.id,
|
||||
],
|
||||
securityGroups: [
|
||||
securityGroup.id,
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user