From a04f25168580f5dfc7e7a7aea3e106d57c187990 Mon Sep 17 00:00:00 2001 From: Michele Cereda Date: Wed, 16 Apr 2025 18:23:56 +0200 Subject: [PATCH] chore(pulumi/components): add standardized aws service account example --- knowledge base/cloud computing/aws/iam.md | 58 +++++++--- knowledge base/pulumi.md | 133 ++++++++++++++++++++-- snippets/aws/cli.fish | 3 + snippets/pulumi/commands.fish | 4 + 4 files changed, 174 insertions(+), 24 deletions(-) diff --git a/knowledge base/cloud computing/aws/iam.md b/knowledge base/cloud computing/aws/iam.md index bd7b684..369804f 100644 --- a/knowledge base/cloud computing/aws/iam.md +++ b/knowledge base/cloud computing/aws/iam.md @@ -65,13 +65,15 @@ Refer [aws.permissions.cloud] for a community-driven source of truth for AWS IAM ## Users -Refer [IAM users]. +Refer [IAM Users]. -Represent a human user or workload needing to interact with AWS resources.
+Represent human users or workloads needing to interact with AWS resources.
Consist of a name and credentials.
Applications using their credentials to make requests are typically referred to as _service accounts_. -IAM Users with administrator permissions are **not** the same thing as the AWS account's root user. +IAM Users with administrator permissions are **not** the same thing as the AWS Account's root user.
+The root user is **required** to perform some [specific tasks][tasks that require root user credentials] on the account, +which will **not** be available if signed in as any other user. IAM identifies IAM Users via: @@ -140,7 +142,16 @@ Their _effect_ can be to `allow` or `deny` such actions. A `deny` statement **al Mostly stored as structured JSON documents.
Each Policy comes with one or several _statements_. Each statement defines an effect. -IAM does not expose Policies' `Sid` element in the IAM API, so it can't be used to retrieve statements. +IAM does **not** expose Policies' `Sid` element in the IAM API, so it **cannot** be used to filter retrieved statements. + +Logical evaluation: + +- **Statements** in a Policy operate in an `OR` fashion.
+ As in, **at least one** statement must allow access to a set of resources. +- **Conditions** in a Statement operate in an `AND` fashion.
+ As in, **all** conditions must resolve true for the statement to allow access. +- **Operator values** in a Condition operate in an `OR` fashion.
+ As in, **at least one** value must match for a Condition to resolve true. Policy examples: @@ -202,7 +213,7 @@ Policy examples: ### Trust Policies Specific type of resource-based policy for IAM roles.
-Used to allow Principals ans AWS Services to assume Roles. +Used to allow Principals and other AWS Services to assume Roles. ### Trust Relationships @@ -245,7 +256,6 @@ Principals and AWS Services can assume Roles as long as: "Version": "2012-10-17", "Statement": [ { - "Sid": "AllowMeToAssumeThoseRoles", "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ @@ -287,7 +297,7 @@ Principals and AWS Services can assume Roles as long as: Allowed entities can assume Roles using the [STS AssumeRole API][assumerole api reference]. -
+
```sh $ aws sts assume-role --role-arn "arn:aws:iam::012345678901:role/EksAdminRole" \ @@ -316,7 +326,7 @@ Refer [Using AWS CLI Securely with IAM Roles and MFA]. Add the `"Bool": {"aws:MultiFactorAuthPresent": true}` condition to the Role's Trust Relationships. -
+
```json { @@ -338,13 +348,32 @@ Add the `"Bool": {"aws:MultiFactorAuthPresent": true}` condition to the Role's T
-When requiring MFA with AssumeRole, identities need to pass values for the SerialNumber and TokenCode parameters.
-SerialNumbers identify the users' hardware or virtual MFA devices, TokenCodes are the time-based one-time password -(TOTP) value that devices produce. +When requiring MFA with AssumeRole, identities **must** pass values for the `SerialNumber` and `TokenCode` +parameters.
+`SerialNumber`s identify the users' hardware or virtual MFA devices, while `TokenCode`s are the time-based one-time +password (TOTP) value that devices produce. -For CLI access, the user will need to add the `mfa_serial` setting to their profile. +
-
+```sh +$ aws sts assume-role --output 'yaml' --duration-seconds '900' \ + --role-arn 'arn:aws:iam::012345678901:role/EksAdminRole' --role-session-name 'lookAt-him-heIsThe-EksAdmin-now' \ + --serial-number 'arn:aws:iam::012345678901:mfa/gopass' --token-code '123456' +AssumedRoleUser: + Arn: arn:aws:sts::012345678901:assumed-role/EksAdminRole/lookAt-him-heIsThe-EksAdmin-now + AssumedRoleId: AROA2HKHF74L72AABBCCDD:lookAt-him-heIsThe-EksAdmin-now +Credentials: + AccessKeyId: ASIA2HKHF74L7YOAUZHR + Expiration: '2025-04-12T08:09:46+00:00' + SecretAccessKey: ErhyPKjQkI3GbrnszpOvMTi8AvmziGbSIOIcNS9k + SessionToken: IQoJb3JpZ2…LxEOLkm9U +``` + +
+ +For CLI access, users will **need** to add the `mfa_serial` setting to their profile. + +
```ini [default] @@ -372,6 +401,7 @@ UserId: AROA2HKHF74L72AABBCCDD:botocore-session-1234567890 - [aws.permissions.cloud] - [Using service-linked roles] - [IAM and AWS STS quotas] +- [AWS global condition context keys] ### Sources @@ -412,6 +442,7 @@ UserId: AROA2HKHF74L72AABBCCDD:botocore-session-1234567890 [assumerole api reference]: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html +[aws global condition context keys]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html [can i increase the duration of the iam role chaining session?]: https://repost.aws/knowledge-center/iam-role-chaining-limit [creating a role to delegate permissions to an iam user]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html [how can i monitor the account activity of specific iam users, roles, and aws access keys?]: https://repost.aws/knowledge-center/view-iam-history @@ -423,6 +454,7 @@ UserId: AROA2HKHF74L72AABBCCDD:botocore-session-1234567890 [iam user groups]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_groups.html [iam users]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html [not authorized to perform: sts:assumerole]: https://repost.aws/questions/QUOY5XngCtRyOX4Desaygz8Q/not-authorized-to-perform-sts-assumerole +[tasks that require root user credentials]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#root-user-tasks [troubleshooting iam roles]: https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_roles.html [use an iam role in the aws cli]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html [using iam policy conditions for fine-grained access control to manage resource record sets]: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/specifying-rrset-conditions.html diff --git a/knowledge base/pulumi.md b/knowledge base/pulumi.md index 7666522..359122d 100644 --- a/knowledge base/pulumi.md +++ b/knowledge base/pulumi.md @@ -857,7 +857,7 @@ backend: FIXME: should this be under [Program]? -Refer [Component resources]. +Refer [Component resources] and [Create a ComponentResource]. Logical grouping of resources.
Usually leveraged to instantiate a set of related resources, aggregate them as children, and create larger abstractions @@ -882,6 +882,19 @@ Refer [Pulumi Crosswalk for AWS] or [Google Cloud Static Website] as examples.
+1. Declare the types of resources one wants to export from the class. + +
+ + ```ts + class StandardAwsVpc extends pulumi.ComponentResource { + internetGateway: aws.ec2.InternetGateway + vpc: aws.ec2.Vpc + }; + ``` + +
+ 1. Inside its constructor, chain to the base constructor and pass it the subclass' name, arguments, and options. Upon creation of a new instance of the Component, the call to the base constructor registers the instance with the @@ -892,10 +905,12 @@ Refer [Pulumi Crosswalk for AWS] or [Google Cloud Static Website] as examples. Components must also register a unique _type name_ with the base constructor. These names are namespaced alongside non-Component resources such as `aws:lambda:Function`. -
+
```ts class StandardAwsVpc extends pulumi.ComponentResource { + … + constructor(name: string, args: pulumi.Inputs, opts?: pulumi.ComponentResourceOptions) { super("exampleOrg:StandardAwsVpc", name, {}, opts); }; @@ -911,15 +926,17 @@ Refer [Pulumi Crosswalk for AWS] or [Google Cloud Static Website] as examples. ```ts class StandardAwsVpc extends pulumi.ComponentResource { + … + constructor(name: string, args: pulumi.Inputs, opts?: pulumi.ComponentResourceOptions) { … - const vpc = new aws.ec2.Vpc( + this.vpc = new aws.ec2.Vpc( `${name}`, { … }, { parent: this }, ); - const internetGateway = new aws.ec2.InternetGateway( + this.internetGateway = new aws.ec2.InternetGateway( `${name}`, { vpcId: vpc.id, @@ -946,7 +963,7 @@ Refer [Pulumi Crosswalk for AWS] or [Google Cloud Static Website] as examples. … this.registerOutputs({ - vpcId: vpc.id, + vpcId: this.vpc.id, }); }; }; @@ -972,16 +989,21 @@ Refer [Pulumi Crosswalk for AWS] or [Google Cloud Static Website] as examples.
- Sample code + Example: standardized AWS VPC + +FIXME: check ```ts import * as aws from "@pulumi/aws"; export class StandardAwsVpc extends pulumi.ComponentResource { + internetGateway: aws.ec2.InternetGateway + vpc: aws.ec2.Vpc + constructor(name: string, args: pulumi.Inputs, opts?: pulumi.ComponentResourceOptions) { super("exampleOrg:StandardAwsVpc", name, {}, opts); - const vpc = new aws.ec2.Vpc( + this.vpc = new aws.ec2.Vpc( `${name}`, { tags: { @@ -994,7 +1016,7 @@ export class StandardAwsVpc extends pulumi.ComponentResource { }, { parent: this }, ); - const internetGateway = new aws.ec2.InternetGateway( + this.internetGateway = new aws.ec2.InternetGateway( name, { tags: { @@ -1002,14 +1024,14 @@ export class StandardAwsVpc extends pulumi.ComponentResource { ...args.tags, }, - vpcId: vpc.id, + vpcId: this.vpc.id, }, - { parent: vpc }, + { parent: this.vpc }, ); … this.registerOutputs({ - vpcId: vpc.id, + vpcId: this.vpc.id, }); }; }; @@ -1029,6 +1051,93 @@ const currentVpc = new StandardAwsVpc(
+
+ Example: standardized AWS service role + +```ts +import * as pulumi from '@pulumi/pulumi'; +import * as aws from '@pulumi/aws'; + +class StandardServiceRole extends pulumi.ComponentResource { + assumeRole: { + iamPolicy: aws.iam.Policy + } + iamRole: aws.iam.Role + + constructor( + name: string, + args: aws.iam.RoleArgs, + opts?: pulumi.ComponentResourceOptions, + ) { + super('exampleOrg:StandardServiceRole', name, {}, opts); + + this.iamRole = new aws.iam.Role( + name, + { + ...args, + tags: { + ServiceRole: 'true', + StandardResource: 'true', + ...args.tags, + }, + }, + { parent: this }, + ); + + this.assumeRole = { + iamPolicy: new aws.iam.Policy( + `${name}-assumeRole`, + { + name: `${args.name}-AssumeRole`, + description: `Allows bearers to try and assume the ${args.name} standard service role`, + tags: { + StandardResource: 'true', + }, + + policy: pulumi.jsonStringify({ + Version: '2012-10-17', + Statement: [{ + Sid: `AllowAssumingThe${args.name}Role`, + Effect: 'Allow', + Action: 'sts:AssumeRole', + Resource: this.iamRole.arn, + }], + }), + }, + { parent: this }, + ), + }; + + this.registerOutputs({ + iamRole: this.iamRole, + assumeRole: this.assumeRole, + }); + }; +}; + +const serviceRole = new StandardServiceRole( + 'someServiceRole', + { + name: 'SomeServiceRole', + description: 'SomeServiceRole', + + assumeRolePolicy: pulumi.jsonStringify({ + Version: '2012-10-17', + Statement: [{ + Effect: 'Allow', + Principal: { + AWS: '012345678901', + }, + Action: 'sts:AssumeRole', + }], + }), + }, +); +serviceRole.assumeRole.iamPolicy.name.apply(policyName => console.log(policyName)); +``` + +
+ ## Import resources FIXME: should this be under [Program] or [Stack]? @@ -1297,6 +1406,7 @@ Solution: follow the suggestion in the warning message: - [`pulumi config set-all`][pulumi config set-all] - [Importing resources] - [Property paths] +- [Create a ComponentResource] [assigning tags by default on aws with pulumi]: https://blog.scottlowe.org/2023/09/11/assigning-tags-by-default-on-aws-with-pulumi/ +[create a componentresource]: https://pulumi.awsworkshop.io/30_modern_iac_ts/45_componens/10_create_component.html [docker images]: https://hub.docker.com/u/pulumi [things i wish i knew earlier about pulumi]: https://vsupalov.com/pulumi-learnings/ diff --git a/snippets/aws/cli.fish b/snippets/aws/cli.fish index bf0384e..7722312 100644 --- a/snippets/aws/cli.fish +++ b/snippets/aws/cli.fish @@ -23,6 +23,9 @@ aws --profile 'engineer' sts assume-role \ aws sts get-caller-identity AWS_PROFILE='engineer' aws sts get-caller-identity +# Clear cached credentials +rm -r ~'/.aws/cli/cache' + # Run as Docker container docker run --rm -ti 'amazon/aws-cli' --version diff --git a/snippets/pulumi/commands.fish b/snippets/pulumi/commands.fish index 4613052..c2b0aa0 100644 --- a/snippets/pulumi/commands.fish +++ b/snippets/pulumi/commands.fish @@ -38,6 +38,7 @@ pulumi config set 'boincAcctMgrUrl' 'https://bam.boincstats.com' pulumi config set --secret 'boincGuiRpcPasswd' 'something-something-darkside' pulumi config set --path 'outer.inner' 'value' pulumi config set --path 'list[1]' 'value' +gpg --export 'smth@example.org' | pulumi config set 'smthTeam:pgpKey-public-raw' --type 'string' # Gitlab provider # 'baseUrl' requires the ending slash @@ -78,6 +79,9 @@ pulumi stack export | jq -r '.deployment.resources[]|{"urn":.urn,"provider":.pro # Check providers are all of a specific version pulumi stack export | jq -r '.deployment.resources[].provider' | grep -v 'aws::default_6_50_0' +# Get the AWS secret access key of an aws.iam.AccessKey resource +pulumi stack output 'someAccessKey' | jq -r '.encryptedSecret' - | base64 -d | gpg --decrypt + # Avoid permission errors when deleting clusters with charts and stuff. PULUMI_K8S_DELETE_UNREACHABLE='true' pulumi destroy