# Terraform Workload Identity [PRD][2] ## Why? > Credentials == #1 Customer Pain * operation concerns * risk * onboarding time > "OIDC" == Trust * ecosystem building around standard * Just in time (JIT) access * Vault JWT Auth Method * AWS, GCP, and Azure support * Custom customer solutions > We will enable power users and set the foundation for mass adoption * JPMC, Snowflake and others will start using JWT immediately * AWS wants to base all authen for their service catalog products on JWTs * Azure is asking for it without knowing we are looking at it * Cross team buy-in on longer term plan * Bring JIT access to the masses Acceptance Criteria: * JWT's are unique for each stage of a run including plan, apply and run tasks. * Generated during speculative plans and standard runs * Exposed as an environment variable in the terraform build environment. * Not stored to state unless practitioner specifically references it from Terraform. * Accessible on agent-based workloads including hooks * Given a TTL that corresponds to the system timeout on the current workload. * JWT metadata must include: * id of the run * workspace id of the run * organization id of the run * stage of the run. i.e. speculative plan, plan, apply, run. To think about: 1. NTP syncronization to ensure time matches from client to server. 1. X.509 certificate expiration management? 1. Who is on the app-sec team? (Brian?) ## Workflow 1. In cloud provider, create OIDC trust between cloud role and TFC Workspace. 1. In run task, generate a token via OIDC Provider before apply step. 1. Inject secrets into run. ```plaintext |<--- 1. fetch .well-known/openid-configuration | | |---- 2. return public keys ------->| | | ------- ---------------- ---------- --------------------- | TFC | | OIDC Provider| | runner | | AWS/Azure/GCP/Vault | ------- ---------------- ---------- --------------------- | | | | |- 3. request id token ->| | | | | | | |<-4. return id token ---| | | | | | |>--5. provide id token ------------------------------------>| | | |<---6. return secrets ------------------------------------<| | | | |---7. inject secrets ------------------->| | | | |-- 8. do things ->| ``` 1. the identity provider is registered with AWS/Azure/GCP/Vault. * the `/.well-known/openid-configuration` endpoint is used to fetch metadata. 2. the openid metadata is returned. * this metadata is used to validate the integrity of JWT's that it receives in step 5. ```bash モ curl https://dev-klipadbq.us.auth0.com/.well-known/openid-configuration | jq { "issuer": "https://dev-klipadbq.us.auth0.com/", "authorization_endpoint": "https://dev-klipadbq.us.auth0.com/authorize", "token_endpoint": "https://dev-klipadbq.us.auth0.com/oauth/token", "device_authorization_endpoint": "https://dev-klipadbq.us.auth0.com/oauth/device/code", "userinfo_endpoint": "https://dev-klipadbq.us.auth0.com/userinfo", "mfa_challenge_endpoint": "https://dev-klipadbq.us.auth0.com/mfa/challenge", "jwks_uri": "https://dev-klipadbq.us.auth0.com/.well-known/jwks.json", "registration_endpoint": "https://dev-klipadbq.us.auth0.com/oidc/register", "revocation_endpoint": "https://dev-klipadbq.us.auth0.com/oauth/revoke", "scopes_supported": [ "openid", "profile", "offline_access", "name", "given_name", "family_name", "nickname", "email", "email_verified", "picture", "created_at", "identities", "phone", "address" ], "response_types_supported": [ "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token" ], "code_challenge_methods_supported": [ "S256", "plain" ], "response_modes_supported": [ "query", "fragment", "form_post" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "HS256", "RS256" ], "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ], "claims_supported": [ "aud", "auth_time", "created_at", "email", "email_verified", "exp", "family_name", "given_name", "iat", "identities", "iss", "name", "nickname", "phone_number", "picture", "sub" ], "request_uri_parameter_supported": false } ``` 3. TFC starts a plan, apply etc. by requesting (generating?) an `id_token` * `id_token` can be obtained via an authz code flow, implicit flow or hybrid flow. ```bash curl -s \ -u "client_id:client_secret" \ --basic \ -d "grant_type=authorization_code&code=example&redirect_uri=http://localhost:3000/oauth/callback" \ "https://dev-klipadbq.us.auth0.com/oauth/token" ``` 4. the `id_token` that is returned is a signed JWT * signature is a hash of content encrypted with private key. * signature can be decrypted by public key found in metadata exchange. * hash is recomputed by aws to ensure that the data hasn't been tampered with. ```json { "access_token":"eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9kZXYta2xpcGFkYnEudXMuYXV0aDAuY29tLyJ9..kpYUjY8jDc2kRVat.noMdBQplkqhibQhYsz5pWr84Y9KkYl5XsS4IPQhq0EOa0nsy7tqAyH0viGKsFNZ2qxnckE2qk7YqDPGsbgSR_pQlsxGODllWEjVxhRRSDehHkWf9h5rBsMS0bVPbHRbRp_z9hSmzXdtd3xRWHgMN35tu3cylRqnWLgp4bVZF8UA4sEPHe6wZFWrkPq_YCYhTDGoFsxk-6WXy4_r6xKIttWeXKEA0bTADsERKBTfRNI5F7F4-iTHd9VQl4HItoRfDz48cp86LD1AiuES-mMtPgP9HaWbqo9O4UDk9NXs_awhKfyNy.uNjG1xpLDnc4Km9oHcFwNg", "id_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlE2T2V2XzZTMkhDa3VKVFdQNzlrdCJ9.eyJpc3MiOiJodHRwczovL2Rldi1rbGlwYWRicS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI1NjM4ODhmN2YwZjUwMDY4M2Y3YTllIiwiYXVkIjoieW1oaDF0clo4VGNxVVBqM1hlWm96MjdLalhQaEFtaE8iLCJpYXQiOjE2NDk4MjMxNTMsImV4cCI6MTY0OTg1OTE1M30.R_zm-f2e6ZVKW73ycPXbBtoBk8gGNytiol4ET4RtNTmgTElFHNDUmyHJDTJbzyHACOju5RcR4o0kpwxdkCmZy7iQw1U4JphAhb1Na4wKHJqk7zn1wJ-hrHou7QGnuVufQTvLnxyxcF6AMc5gxi3CbQf1p5NH0JQpdgc7R-tiQDMV4sa7mWMdVVNHv7oIJFu7PKuPKkg9Aox71dtq-e5_Ucth7JNkDHV61xvJ3L6UoPk9BVDqj4pT84T_ucfKsREdS-_6_2vTneDycxl1UW7_e3UTXpvsaui87LKSBs4L2feOuWTxRL62-XKN--D02tLY6nLdu-OAFEnLy1NX8h0qqA", "scope":"openid", "expires_in":86400, "token_type":"Bearer" } ``` ```irb irb(main):001:0> require 'base64' irb(main):002:0> x = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlE2T2V2XzZTMkhDa3VKVFdQNzlrdCJ9.eyJpc3MiOiJodHRwczovL2Rldi1rbGlwYWRicS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI1NjM4ODhmN2YwZjUwMDY4M2Y3YTllIiwiYXVkIjoieW1oaDF0clo4VGNxVVBqM1hlWm96MjdLalhQaEFtaE8iLCJpYXQiOjE2NDk4MjMxNTMsImV4cCI6MTY0OTg1OTE1M30.R_zm-f2e6ZVKW73ycPXbBtoBk8gGNytiol4ET4RtNTmgTElFHNDUmyHJDTJbzyHACOju5RcR4o0kpwxdkCmZy7iQw1U4JphAhb1Na4wKHJqk7zn1wJ-hrHou7QGnuVufQTvLnxyxcF6AMc5gxi3CbQf1p5NH0JQpdgc7R-tiQDMV4sa7mWMdVVNHv7oIJFu7PKuPKkg9Aox71dtq-e5_Ucth7JNkDHV61xvJ3L6UoPk9BVDqj4pT84T_ucfKsREdS-_6_2vTneDycxl1UW7_e3UTXpvsaui87LKSBs4L2feOuWTxRL62-XKN--D02tLY6nLdu-OAFEnLy1NX8h0qqA' => "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlE2T2V2XzZTMkhDa3VKVFdQNzlrdCJ9.eyJpc3MiOiJodHRwczovL2Rldi1rbGlwYWRicS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI1NjM4ODhmN2YwZjUwMDY4M2Y3YTllIiwiYXVkIjoieW1oaDF0clo4VGNxVVBqM1hlWm96MjdLalhQaEFtaE8iLCJpYXQiOjE2NDk4MjMxNTMsImV4cCI6MTY0OTg1OTE1M... irb(main):003:0> x.split('.') => ["eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlE2T2V2XzZTMkhDa3VKVFdQNzlrdCJ9", "eyJpc3MiOiJodHRwczovL2Rldi1rbGlwYWRicS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI1NjM4ODhmN2YwZjUwMDY4M2Y3YTllIiwiYXVkIjoieW1oaDF0clo4VGNxVVBqM1hlWm96MjdLalhQaEFtaE8iLCJpYXQiOjE2NDk4MjMxNTMsImV4cCI6MTY0OTg1OTE1M30", "R_zm-f2e6ZVKW73ycPXbBtoBk8gGNytiol4ET4RtNTmgTElFHNDUmyHJDTJbzyHACOju5RcR4o0kpwxdkCmZy7iQw1U4JphAhb1Na4wKHJqk7zn1wJ-hrHou7QGnuVufQTvLnxyxcF6AMc5gxi3CbQf1p5NH0JQpdgc7R-tiQDMV4sa7mWMdVVNHv7oIJFu7PKuPKkg9Aox71dtq-e5_Ucth7JNkDHV61xvJ3L6UoPk9BVDqj4pT84T_ucfKsREdS-_6_2vTneDycxl1UW7_e3UTXpvsaui87LKSBs4L2feOuWTxRL62-XKN--D02tLY6nLdu-OAFEnLy1NX8h0qqA"] irb(main):004:0> Base64.decode64(x.split('.')[0]) => "{\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"Q6Oev_6S2HCkuJTWP79kt\"}" irb(main):005:0> Base64.decode64(x.split('.')[1]) => "{\"iss\":\"https://dev-klipadbq.us.auth0.com/\",\"sub\":\"auth0|62563888f7f0f500683f7a9e\",\"aud\":\"ymhh1trZ8TcqUPj3XeZoz27KjXPhAmhO\",\"iat\":1649823153,\"exp\":1649859153}" ``` 5. TFC sends the `id_token` to the token service for the cloud provider. e.g. [STS][1] ```bash モ aws sts assume-role-with-web-identity \ --role-arn "arn:aws:iam::119201842733:role/idp-web-identity-role" \ --role-session-name="example-1" \ --duration-seconds 3600 \ --web-identity-token="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlE2T2V2XzZTMkhDa3VKVFdQNzlrdCJ9.eyJpc3MiOiJodHRwczovL2Rldi1rbGlwYWRicS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI1NjM4ODhmN2YwZjUwMDY4M2Y3YTllIiwiYXVkIjoieW1oaDF0clo4VGNxVVBqM1hlWm96MjdLalhQaEFtaE8iLCJpYXQiOjE2NDk4MjMxNTMsImV4cCI6MTY0OTg1OTE1M30.R_zm-f2e6ZVKW73ycPXbBtoBk8gGNytiol4ET4RtNTmgTElFHNDUmyHJDTJbzyHACOju5RcR4o0kpwxdkCmZy7iQw1U4JphAhb1Na4wKHJqk7zn1wJ-hrHou7QGnuVufQTvLnxyxcF6AMc5gxi3CbQf1p5NH0JQpdgc7R-tiQDMV4sa7mWMdVVNHv7oIJFu7PKuPKkg9Aox71dtq-e5_Ucth7JNkDHV61xvJ3L6UoPk9BVDqj4pT84T_ucfKsREdS-_6_2vTneDycxl1UW7_e3UTXpvsaui87LKSBs4L2feOuWTxRL62-XKN--D02tLY6nLdu-OAFEnLy1NX8h0qqA" --output json | cat ``` 6. Cloud provider returns secrets. ```json { "Credentials": { "AccessKeyId": "ASIARXQH3YYWQMPAN57M", "SecretAccessKey": "nRDvl9q6xoDtvLhha16mOQse9r03XoL2Yw6yMtwp", "SessionToken": "FwoGZXIvYXdzEL7//////////wEaDCdzgBb1TNJKqqghGSLXAim8vKlHEDn/MxcIsIWnPloA+muPpcvh27zTlKg0XOAVHP6gHudFZHtHJY3L6Lnisf6np/7rstaHqGQy6Fa9tr47KHZtkhQRaDcrpKMgGc9v3VqP4g72k3NNg2zgzWThJEfvNge/RyabpIY1oqju9rK9CS3I7y8ntW4e/8KTxeYIniWgAPBrFJlnDm+vahTtsg/04OkuL+iIpfI+FaUBzTdzZGaE3cKztJX1e/0NpmOGLx5HCkaF0tn9MeZBeSJ/Z/pF4fAZK12+uN0pLvZOkMta+lFhMWlbzqF6pPiruF6PUv6iGILyE85JA/uM6ZerV3RiF4YdxyO5NOnq4USRzdxLQq9IqD5aZNeT1ZnbDPZR4yhztni01t6MB5DtXpaweRdFGEK/cT0aquOGYjYs6qVjuZOnqrUdObBk/V9R9TYuhur2StzyWeXozWSehfr/j/qovO39y1Mo9J3ZkgYyJArNFYwaGf/X2emoBDq/Jl5V2hEiFxANPW7LAhquWzIwIjDQBA==", "Expiration": "2022-04-13T05:17:56+00:00" }, "SubjectFromWebIdentityToken": "auth0|62563888f7f0f500683f7a9e", "AssumedRoleUser": { "AssumedRoleId": "AROARXQH3YYWSI3UUY4XD:example-1", "Arn": "arn:aws:sts::119201842733:assumed-role/idp-web-identity-role/example-1" }, "Provider": "arn:aws:iam::119201842733:oidc-provider/dev-klipadbq.us.auth0.com/", "Audience": "ymhh1trZ8TcqUPj3XeZoz27KjXPhAmhO" } ``` 7. TFC injects secrets into the `Run`. I don't know how. Config variables, maybe? 8. The `Run` makes API calls to the cloud provider using the `AWS_ACCESS_KEY_ID` and`AWS_SECRET_KEY` env vars that were injected. [1]: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html [2]: https://docs.google.com/document/d/1IGSX1eSk6zQw1Fk0LUuJ9KgeEZgQoPg7126NfnQbL1M/edit#