{"id":"l-aws-2-3","courseId":"aws-cloud-foundations","lang":"en","kind":"lesson","summary":"Give people access through IAM Identity Center and code access through IAM roles, so that Lantern runs with no long-term access keys on laptops, servers or in Git.","html":"<p>Section one ended with one admin user for one person in one account. Lantern now has six volunteers, a dev account and a production account, a Lambda function and a nightly job on EC2. If every one of those needs an IAM user with access keys, you end up with a dozen long-lived secrets scattered across laptops and servers. This lesson removes nearly all of them.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">People: IAM Identity Center</h2>\n<p><strong>AWS IAM Identity Center</strong> is where your workforce signs in. Each person gets <strong>one</strong> identity, either in Identity Center’s built-in directory or synced from an identity provider such as Microsoft Entra ID, Okta or Google Workspace. They sign in once, with MFA, at an <strong>AWS access portal</strong> URL, and see every account they are allowed into.</p>\n<p>What they can do in each account comes from <strong>permission sets</strong>: a named bundle of policies, such as <code dir=\"ltr\">DeveloperAccess</code> or <code dir=\"ltr\">ReadOnly</code>. When you assign “volunteer Rita, permission set <code dir=\"ltr\">DeveloperAccess</code>, account <code dir=\"ltr\">lantern-dev</code>”, Identity Center creates a matching IAM role in that account, and Rita’s session assumes it. Nobody has an IAM user; nobody has a long-term key.</p>\n<p>Identity Center is designed to run with <strong>AWS Organizations</strong>, which groups Lantern’s accounts under one management account. Note one practical catch: on the Free account plan, joining an organisation upgrades the account to the Paid plan, so this is a step for when Lantern goes to production.</p>\n<p>On the CLI, <code dir=\"ltr\">aws configure sso</code> writes a profile, and <code dir=\"ltr\">aws sso login</code> opens the browser to sign in:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ini\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">~/.aws/config</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">[sso-session lantern]</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_start_url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = https://lantern.awsapps.com/start</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_region</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = eu-west-1</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_registration_scopes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = sso:account:access</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">[profile lantern-dev]</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_session</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = lantern</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_account_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = 444455556666</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">sso_role_name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = DeveloperAccess</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">region</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> = eu-west-1</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> sso</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> login</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --sso-session</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> sts</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get-caller-identity</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --profile</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern-dev</span></span></code></pre></div>\n<p>The <code dir=\"ltr\">Arn</code> that comes back now reads <code dir=\"ltr\">assumed-role/AWSReservedSSO_DeveloperAccess_…/rita</code>: a role session, not a user.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Workloads: roles, not keys</h2>\n<p>Code needs credentials too, and the answer is always a <strong>role</strong> that the platform assumes for you:</p>\n<ul>\n<li><strong>Lambda</strong>: every function has an <strong>execution role</strong>.</li>\n<li><strong>EC2</strong>: an <strong>instance profile</strong> delivers the role’s credentials through the instance metadata service.</li>\n<li><strong>Amazon ECS</strong>: a <strong>task role</strong> per task definition.</li>\n<li><strong>CI/CD outside AWS</strong>, such as GitHub Actions: an OpenID Connect identity provider in IAM, so the pipeline exchanges its own token for a role session.</li>\n</ul>\n<p>A role always has two policies. The <strong>trust policy</strong> says who may assume it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">trust-policy.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Version\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2012-10-17\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Statement\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Allow\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Principal\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"Service\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"lambda.amazonaws.com\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sts:AssumeRole\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The <strong>permissions policy</strong> says what the role may do once assumed, written exactly like last lesson’s policies.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-aws-2-3-t1\"><defs><marker id=\"arrow-l-aws-2-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-aws-2-3-t1\">The Lambda service asks STS to assume the lantern-rsvp role. STS checks the trust policy and returns temporary credentials. The function code uses them to call DynamoDB, where IAM checks the role's permissions policy.</title>\n  <rect class=\"d-box-primary\" x=\"20\" y=\"80\" width=\"150\" height=\"64\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"95\" y=\"108\" text-anchor=\"middle\">Lambda</text>\n  <text class=\"d-label-muted\" x=\"95\" y=\"128\" text-anchor=\"middle\">rsvp function</text>\n  <rect class=\"d-box-accent\" x=\"265\" y=\"20\" width=\"150\" height=\"64\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"48\" text-anchor=\"middle\">AWS STS</text>\n  <text class=\"d-label-muted\" x=\"340\" y=\"68\" text-anchor=\"middle\">checks trust policy</text>\n  <rect class=\"d-box-success\" x=\"510\" y=\"140\" width=\"150\" height=\"64\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"585\" y=\"168\" text-anchor=\"middle\">DynamoDB</text>\n  <text class=\"d-label-muted\" x=\"585\" y=\"188\" text-anchor=\"middle\">checks permissions</text>\n  <path class=\"d-arrow\" d=\"M170 96 L262 56\" marker-end=\"url(#arrow-l-aws-2-3-1)\"/>\n  <text class=\"d-label\" x=\"160\" y=\"58\">1 AssumeRole</text>\n  <path class=\"d-arrow d-dashed\" d=\"M265 72 L174 112\" marker-end=\"url(#arrow-l-aws-2-3-1)\"/>\n  <text class=\"d-label\" x=\"232\" y=\"120\">2 temp credentials</text>\n  <path class=\"d-arrow\" d=\"M170 134 L506 170\" marker-end=\"url(#arrow-l-aws-2-3-1)\"/>\n  <text class=\"d-label\" x=\"300\" y=\"176\">3 PutItem, signed</text>\n</svg><figcaption>Lambda assumes its execution role, then calls DynamoDB with temporary credentials</figcaption></figure>\n<p>Your code then contains <strong>no credentials at all</strong>. The AWS SDKs use a default credential chain that finds the role’s temporary credentials automatically:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">rsvp/index.mjs</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { DynamoDBClient } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"@aws-sdk/client-dynamodb\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// No keys here: the SDK finds the execution role's temporary credentials.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> client</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> DynamoDBClient</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({});</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …handler code that calls client.send(…)</span></span></code></pre></div>\n<h2 id=\"sec-3\" tabindex=\"-1\">The mistake that ends in a large bill</h2>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Access keys in code, config or Git</span></p>\n<p>A line like <code dir=\"ltr\">accessKeyId: &quot;AKIA…&quot;</code> in source, a committed <code dir=\"ltr\">.env</code> file, or a key pasted into a CI variable “for now” is one of the most common ways AWS accounts get compromised. Bots scan public repositories continuously, and a leaked key can be running expensive instances within minutes. AWS may attach a quarantine policy to a key it finds exposed, but that limits damage after the fact. The fix is structural: roles for workloads, Identity Center or <code dir=\"ltr\">aws login</code> for people, and a secret scanner on the repository.</p>\n</aside>\n<p>Sometimes an access key is genuinely unavoidable, such as a third-party tool that only accepts keys. Then: one IAM user per tool, a least-privilege policy, a <code dir=\"ltr\">aws:SourceIp</code> condition if the tool has fixed addresses, and rotation on a schedule.</p>\n<p>Retiring an existing key is a careful sequence, because deleting a key that something still uses breaks production. Check when it was last used:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> iam</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get-access-key-last-used</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --access-key-id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> AKIAIOSFODNN7EXAMPLE</span></span></code></pre></div>\n<p>Once nothing has used it since the replacement went live, <strong>deactivate</strong> it first (reversible), wait, and <strong>delete</strong> it only when nothing has complained. A key you suspect has leaked is different: deactivate it immediately and investigate afterwards.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Cost and security are the same conversation</span></p>\n<p>A leaked key costs money first and reputation second. Every key you replace with a role is a line removed from your worst-case bill.</p>\n</aside>\n<p>That completes identity. Section three puts it to work as you pick Lantern’s compute, storage, database and network.</p>\n","toc":[{"id":"sec-1","text":"People: IAM Identity Center","level":2},{"id":"sec-2","text":"Workloads: roles, not keys","level":2},{"id":"sec-3","text":"The mistake that ends in a large bill","level":2}],"takeaways":["IAM Identity Center gives each person one sign-in, with MFA, to every account they need, and hands out short-term credentials through permission sets.","Workloads get permissions from roles, such as a Lambda execution role or an EC2 instance profile, and the AWS SDKs find those temporary credentials without any code.","A role has two policies; the trust policy says who may assume it, and the permissions policy says what it may then do.","Long-term access keys in code, config files or environment variables are one of the most common ways AWS accounts get compromised.","When retiring an access key, confirm it is unused with its last-used date, deactivate it, and only then delete it."],"further":[{"title":"What is IAM Identity Center?","url":"https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html"},{"title":"Configuring IAM Identity Center authentication with the AWS CLI","url":"https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html"},{"title":"Defining Lambda function permissions with an execution role","url":"https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html"},{"title":"Manage access keys for IAM users","url":"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"}],"quiz":[{"q":"<p>Lantern’s RSVP function must write to DynamoDB. How should it get credentials?</p>","options":[{"text":"Store an IAM user’s access key in the function’s environment variables.","why":"Anyone who can read the function configuration can read the key, and it never expires. Environment variables are not a secret store."},{"text":"Give the function an execution role with a permissions policy for the table; the SDK picks up its temporary credentials.","why":"Correct. Lambda assumes the role for you and hands each execution environment short-term credentials, so there is nothing to leak or rotate."},{"text":"Use the root user’s credentials, since the function needs reliable access.","why":"Root credentials are unrestricted and should never exist as access keys, let alone inside code."}],"answer":1},{"q":"<p>A role’s permissions policy allows <code dir=\"ltr\">dynamodb:PutItem</code> on the RSVP table, but EC2 instances fail to assume the role. Which policy is most likely wrong?</p>","options":[{"text":"The trust policy, which must name <code dir=\"ltr\">ec2.amazonaws.com</code> as a principal allowed to call <code dir=\"ltr\">sts:AssumeRole</code>.","why":"Correct. Who may assume a role is decided by the trust policy alone. The permissions policy only matters after the role has been assumed."},{"text":"The permissions policy, which must also allow <code dir=\"ltr\">sts:AssumeRole</code>.","why":"A role does not need permission to assume itself. That is the trust policy’s job."},{"text":"The DynamoDB table policy, which must list the EC2 instance ID.","why":"The instance is never the principal in DynamoDB calls; the assumed-role session is. And assumption fails before DynamoDB is involved."}],"answer":0},{"q":"<p>Lantern now has separate dev and production accounts and six volunteers. What is the best way to give the volunteers console and CLI access?</p>","options":[{"text":"An IAM user per volunteer in each account, each with its own password and MFA.","why":"That is twelve sets of credentials to manage and revoke. It works, but it scales badly and leaves long-term credentials behind."},{"text":"One shared IAM user per account with a strong password kept in a password manager.","why":"Shared users destroy accountability and make removing one person impossible without rotating everyone’s access."},{"text":"IAM Identity Center users, with permission sets assigned per account, signing in through the access portal and <code dir=\"ltr\">aws sso login</code>.","why":"Correct. Each person has one identity and MFA, access is short-term, and removing someone is a single change."}],"answer":2}],"exercise":{"kind":"order","items":["write-policy","create-role","attach-profile","remove-keys","verify-identity","check-last-used","deactivate","delete"],"title":"Retire the nightly job's access key","prompt":"<p>Lantern’s nightly export job runs on an EC2 instance and reads an access key from <code dir=\"ltr\">/etc/lantern/.env</code>. The key has <strong>not</strong> leaked; you are replacing it with a role during a quiet week, without breaking the job. Put the steps in a safe order.</p>\n","hints":["The role must exist, with the right permissions, before anything can use it.","Prove the job is running as the role before you touch the old key.","Removing access is done in two stages: one reversible, one permanent."],"explanation":"<p>You build the replacement first: a least-privilege policy, a role whose trust policy allows <code dir=\"ltr\">ec2.amazonaws.com</code>, and an instance profile that puts the role on the instance. Then you remove the key from the instance so the SDK falls through to the role’s credentials, and you prove it with <code dir=\"ltr\">aws sts get-caller-identity</code> on the instance, which should show an <code dir=\"ltr\">assumed-role</code> ARN. Only then do you look at the old key: <code dir=\"ltr\">get-access-key-last-used</code> confirms nothing has used it since the switch. Deactivating is reversible if something you forgot breaks; deleting is final, so it comes last. If the key had leaked, you would deactivate it first, immediately, and accept the breakage.</p>\n","checkLabels":{},"itemText":{"write-policy":"Write a least-privilege permissions policy for exactly what the export job does.","create-role":"Create a role with a trust policy for <code dir=\"ltr\">ec2.amazonaws.com</code> and attach the permissions policy.","attach-profile":"Attach the role to the EC2 instance through an instance profile.","remove-keys":"Remove the access key from <code dir=\"ltr\">/etc/lantern/.env</code> and restart the job.","verify-identity":"On the instance, run <code dir=\"ltr\">aws sts get-caller-identity</code> and confirm the ARN is an <code dir=\"ltr\">assumed-role</code> session.","check-last-used":"Run <code dir=\"ltr\">aws iam get-access-key-last-used</code> after the next nightly run to confirm the old key is idle.","deactivate":"Deactivate the old access key and watch the next few runs.","delete":"Delete the old access key."}},"words":668,"readMinutes":3}