anyone got an example of adding a trust relationsh...
# aws
p
anyone got an example of adding a trust relationship (assume_role_policy) to an IAM role that was created on earlier pulumi up ?
b
which language?
p
python pls
b
p
will give it a try, thx
@billowy-army-68599 when extending an already existing assume_role_policy, is there a neat way to append to the existing role’s assume_role_policy
b
if the API supports it it should just work, is it trying to do a delete/replace?
p
that was what i saw last week when i tried to apply over the top of the existing role, yes
b
Can you share a little bit more about what you’re trying to add so I can report it
Repro*
p
ok so this is not exactly the code for privacy reasons but essentially a sanitised version of same
the role is created, used to make eks cluster then after the oidc provider is created it must be updated to trust that provider
so there is a cyclic dependency of sorts
Copy code
"""An AWS Python Pulumi program"""

import json
import pulumi
from itplat_region_providers import providers
import pulumi_aws as aws


provider_opts = pulumi.ResourceOptions(provider=providers['us-east-1'])

admin_role_name = "itplat_eks_clusteradmin_role"
eks_admin_role = aws.iam.Role(
    admin_role_name,
    assume_role_policy='''{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":["<http://ec2.amazonaws.com|ec2.amazonaws.com>","<http://eks.amazonaws.com|eks.amazonaws.com>"]},"Action":"sts:AssumeRole"}]}''',
    name=admin_role_name,
    tags={
        "clusterAccess": "itplat_eks_admin",
    },
    opts=pulumi.ResourceOptions(provider=providers['us-east-1']),
)

eks_cluster_vpc_config = {
    "endpoint_private_access": True,
    "endpoint_public_access": False,
    "public_access_cidrs": [
        "0.0.0.0/0",
    ],
    "security_group_ids": [
        "eks_securitygroup.id",
    ],
    "subnet_ids": ["subnet-0e0ff2e099999c840", "subnet-0b962f999996f624b", "subnet-09cc9999998dc4474"],
}

eks_cluster_config = {"role_arn": eks_admin_role.arn, "version": "1.19", "vpc_config": eks_cluster_vpc_config,
                      'name': "itplat-eks-cluster"}

# create cluster resource
eks_cluster = aws.eks.Cluster("itplat-eks-cluster", opts=provider_opts, **eks_cluster_config)

# create oidc provider for cluster
eks_oidc_provider = aws.iam.OpenIdConnectProvider("itplat_eks_oidc_provider",
                                                  client_id_lists=[
                                                      "<http://sts.amazonaws.com|sts.amazonaws.com>"],
                                                  thumbprint_lists=["9e99a48a999999999999999922da2b0ab7280"],
                                                  url=eks_cluster.identities[0].oidcs[0].issuer)

# create the trust relationship (assume_role_policy) for the eks_admin_role
crossplane_provider_trust_relationship = '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":"' + \
eks_oidc_provider.arn.apply(lambda arn: f"{arn}") + \
'"},"Action":"sts:AssumeRoleWithWebIdentity","Condition":{"StringLike":{"' + \
eks_oidc_provider.id.apply(lambda id: f"{id}") + \
':sub": "system:serviceaccount:crossplane-system:provider-aws-*"'

# Merge the oidc trust with the existing role trust
current_trust = json.loads(eks_admin_role.assume_role_policy.apply(lambda trust: f"{trust}"))
cpt = json.loads(crossplane_provider_trust_relationship)
final_trust = {key: value for (key, value) in (current_trust.items() + cpt.items())}

# apply merged trust relationship (assume_role_policy) to the eks_admin_role
eks_admin_role = aws.iam.Role(
    admin_role_name,
    assume_role_policy=json.dumps(final_trust),
    name=admin_role_name,
    tags={
        "clusterAccess": "itplat_eks_admin",
    },
    opts=pulumi.ResourceOptions(provider=providers['us-east-1']),
)
b
okay this is a unique case, you can't declare the role twice. We actually already handle this in our EKS provider, is there a reason you're not using that?
p
because some other pulumi ppl suggested it was better to use the aws provider and exert a more fine grained control over what i was doing that way
and now i have done almost all woth pulumi_aws i dont want to redo it all with pulumi_eks
can you point me at the code that handles this case in the eks provider please
the reason i was recommended to use the pulumi_aws was because pulumi_eks was not obeying providers that let me iterate this same code in several regions
b
i'm just trying to track it down
p
thx
b
ah wait, hang on. the
assumeRoleWithWebIdentity
shouldn't go in the admin role
that's the role that gets assigned to the service account, it's different
p
ok but i do all my iam roles upfront that the eks cluster need
so even the service role pre-exists
b
okay but you're defining two roles with the same name
p
and i accept thats wrong and am after the learning to show me how to do it correctly
i have a role created earlier with a trust that i need to update because of a subsequent condition
in aws, updating an existing role trust_relationship because a thing has changed or been added is a valid use-case
b
I don't have an example in Python, I can try and write one tomorrow. Here's some links in typescript: - You need the oidc ARN and the URL which you already have, because you're using it in an
apply()
- create a completely distinct role called
crossplaneRole
and build the IAM document using that info: https://github.com/pulumi/cert-manager-examples/blob/master/examples/letsencrypt/rbac.ts#L14-L28 I'm not sure what you mean when you say you have a role created "earlier" - do you mean in the code or outside Pulumi?
p
i mean if i was managing roles in the aws IAM ui then this is a valid use-case
made some roles a month ago, new business requirements come up, need to add new trust to an existing role
that type of thing
b
you need to import it into the Pulumi state
pulumi drives to a desired configuration
p
im speaking hypothetically
b
you can just patch it using the AWS python SDK, but it won't be part of the lifecycle
Pulumi cannot manipulate any IAM resources it doesn't know about. If you have existing IAM roles and you want to add something to them, you'll have to get it into Pulumi's state before you can do it
p
in the case i have a role in my pulumi stack and need to add to its trust_relationship but dont want to delete the existing role as it will impact existing running prod service
what is the process ?
b
you'd just update the code with the new trust relationship?
I'm sorry, it's really hard to understand what's going on
p
yeah but in this case its all in the same “pulumi up” run
lol im having the same problem on my side… having trouble understanding how this shoudl work. we are porting something from ansible to pulumi
b
Okay, here's a hypothetical EKS role:
Copy code
eks_role = iam.Role(
    'eks-iam-role',
    assume_role_policy=json.dumps({
        'Version': '2012-10-17',
        'Statement': [
            {
                'Action': 'sts:AssumeRole',
                'Principal': {
                    'Service': '<http://eks.amazonaws.com|eks.amazonaws.com>'
                },
                'Effect': 'Allow',
                'Sid': ''
            }
        ],
    }),
)
p
and in the ansible code we get down to the oidc createion then we update the role
b
okay, ansible and Pulumi work very very differently, so we need to take a step back
You have your EKS role right? the role you use for EKS to work properly as above ^^ then, you define your OIDC connector to enable IAM roles for service accounts:
Copy code
eks_oidc_provider = aws.iam.OpenIdConnectProvider("itplat_eks_oidc_provider",
                                                  client_id_lists=[
                                                      "<http://sts.amazonaws.com|sts.amazonaws.com>"],
                                                  thumbprint_lists=["9e99a48a999999999999999922da2b0ab7280"],
                                                  url=eks_cluster.identities[0].oidcs[0].issuer)
okay, if I'm reafing between the lines correctly, you now want to create a role that allows crossplane to function inside EKS with the right perms, right?
or are you trying to add the OIDC provider to this?
Copy code
eks_role = iam.Role(
    'eks-iam-role',
    assume_role_policy=json.dumps({
        'Version': '2012-10-17',
        'Statement': [
            {
                'Action': 'sts:AssumeRole',
                'Principal': {
                    'Service': '<http://eks.amazonaws.com|eks.amazonaws.com>'
                },
                'Effect': 'Allow',
                'Sid': ''
            }
        ],
    }),
)
p
yes
the latter
is what we achieve in ansible
so i was following the same structure
b
ooookay now I understand, you need this resource: https://www.pulumi.com/docs/reference/pkg/aws/iam/rolepolicyattachment/
which will attach a policy document to an existing resource
but also, that's not the right way to do IAM roles with service accounts 🙂
p
cool, happy to learn some “best practice”
rolepolicyattachment is not for trust_relationship i think
only for regular policies
i am already making use of that
b
the right way to do is to create a whole new distinct role:
Copy code
crossplan_role = iam.Role(
    'crossplane_role',
    assume_role_policy=json.dumps({
       # You'll need an `apply()` in here
    }),
)
add your policy there, then assign THAT role to your k8s service account
p
hmm ok i can try that out
b
you might be right on the role policy thing, i don't think you can do what you want in this case
p
something else … my json.loads(Output.apply) code always fails
b
p
am i doing it wrong ?
ie
Copy code
current_trustz = json.loads(eks_admin_role.assume_role_policy.apply(lambda policy: f"current_trust={policy}"))

print("-== current_trust ==-")
    pprint.pprint(current_trustz)
this was so i could see the json as object
resulted in :-
Copy code
current_trustz = json.loads(eks_admin_role.assume_role_policy.apply(lambda policy: f"current_trust={policy}"))
      File "/Users/bmeehan/.pyenv/versions/3.8.5/lib/python3.8/json/__init__.py", line 341, in loads
        raise TypeError(f'the JSON object must be str, bytes or bytearray, '
    TypeError: the JSON object must be str, bytes or bytearray, not Output
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
oh wait thats the wrong output
b
yeah current_trustz becomes a future, do you have an understanding of how
apply()
works?
p
the whole Output (promise /future) thing is doing my head in
b
p
when i am iterating over pulumi up / add new code block / pulumi up again
then when a resource already exists in stack from last “pulumi up” im expecting its outputs to resolve
but they fail
reading now, thx for the link
b
i have to head to bed, if you're still struggling tomorrow I'm happy to jump on a call to see if we can get this resolved, I'll also build a proper EKS example tomorrow if I get chance
last thing: your
current_trustz
example above, if you want to print it, you do it inside the
apply()
then it'll work, much like the example in the blog post
p
???? like :-
Copy code
current_trustz = json.loads(eks_admin_role.assume_role_policy.apply(lambda policy: pprint.pprint(json.loads(f"{policy}"))))
b
exactly
that should work
p
Copy code
current_trustz = json.loads(eks_admin_role.assume_role_policy.apply(lambda policy: pprint.pprint(json.loads(f"{policy}"))))
      File "/Users/bmeehan/.pyenv/versions/3.8.5/lib/python3.8/json/__init__.py", line 341, in loads
        raise TypeError(f'the JSON object must be str, bytes or bytearray, '
    TypeError: the JSON object must be str, bytes or bytearray, not Output
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
it seems like it is still a type Output
b
ah, your JSON call needs to be inside the apply too
p
erm . . . it is
b
the first one
current_trustz = json.loads <----
this one
p
lol my bad
b
p
is the result of the apply not assigned to the varaible ?
b
yes it will be, but it's resolved when the apply is finished, so it also has a value which is an output
p
x = output.stringthing.apply(lambda y: f’{y}’)
so how do i get it as a string ?
or a json object
see this is where it bites me… i get its a promise, but if i need to do an action against the eventual value of that promise, it fails in the preview phase of “pulumi up”
b
You have an output that you would like to be interpolated into JSON. Since it is an output, there is no way to get it at runtime. This means that your resulting JSON must be a transformation of that output, and thus - also an output. So it should be something like:
current_trustz = eks_admin_role.assume_role_policy.apply(lambda policy: pprint.pprint(json.loads(f"{policy}")))
but I'm confused by why you would want to deserialize JSON and then immediately print it as a string. Do you need just the deserialized JSON at the end? Than it would be:
current_trustz = eks_admin_role.assume_role_policy.apply(lambda policy: json.loads(f"{policy}"))
p
morning, so the intent was to pull the current trust from the role, merge it with the additional stuff trust required and update the role with the merged trust
and also i wanted to look at the structure of the json currently in the role trust policy to understand if my “merge as dict > restore to serialised json” would work
part of iterating towards an eventual solution
im moving forward now with the suggested alternate role for crossplane
but that concept of :- “look at the data structure of the output of a resource i created, figure out how to morph it to what i need, apply it finally as an input to a subsequent resource creation” is the dev loop i still dont have a handle on
i usually dev in pycharm and can see data structures in debug mode so i know how they are layed out. but of course in a pulumi world they are hidden until applied
im thinking maybe my dev loop needs to be 1) pulumi.export the pulumi.Output i want to inspect, 2) work out how to tweek the data structure to what i need outside of pulumi, 3) write a function to achieve that tweek, 4) pass the new resource input value as x.apply(tweek_function(pulumi.Output)… to achieve transformation of the existing resource A output to the required new resource B input
b
I'm still struggling to understand why you need to inspect the values or modify them in anyway, Pulumi is not procedural in any way - what you define is exactly how the data looks. You can't really modify anything that has already defined "on the fly" like this
p
as aws resource have parameters and options that are modify-able, so it follows that we may need to modify / adjust these parameters and options
thus we may want to pull the existing value, and make modifications based upon logic that applies to the current value
this is the core of what i am trying to do
i am iterating over a pulumi stack to create a desired end-state stack that sets up an EKS cluster to how we need it
i add in one component at a time
sometimes the latest resource addition to my pulumi stack then requires me to modify a resource created in a past iteration
b
as aws resource have parameters and options that are modify-able, so it follows that we may need to modify / adjust these parameters and options
If you have a resource you've defined and you update it, Pulumi will perform an
update
action and modify it for you. Is that not what you're seeing? If if you add one resource/component at a time it'll only add the changes you've made
p
so, in this case, i had a role with a trust, i create an oidc provider and then i wanted to add a trust relationship for that to a role that had been created earlier
because this is all in one stack, this does not work as pulumi knows the dependency chain and does things in an order because of this
and acting on the same resource twice in different ways in a single deployment does not work
anyhow its not overly relevant now because i am moving to a different role for crossplane… i get what you are saying generally speaking.
b
right, in your specific example there's a chicken and egg problem, because you're creating 3 resources: - IAM Role - EKS cluster that uses that IAM role - OIDC provider You can't create the cluster without the IAM role, and you can't create the OIDC provider without creating the cluster. The point I'm trying to get across is that there's an inherent reason for this. None of the declarative IaC tools can do this because you're not supposed to do it. Pulumi has resources to attach things to existing roles (the
IamRolePolicyAttachment
) which makes uses of specific API calls if you need to attach objects to other objects, but if it doesn't exist it's because it's not a good idea
p
sure, or the use-case hasnt yet been described/covered
anyhow i think for now i am ok progressing down the seperate role path and trying to figure out how to have to crossplane provider make use of that role instead
it is different to our ansible build, but thats ok as long as i can make it work the same way at the end of the path
i think our helm charts may just need some modification to work with the crossplane role instead of the admin role
Copy code
# create oidc provider for cluster
    eks_oidc_provider = aws.iam.OpenIdConnectProvider("itplat_eks_oidc_provider",
        client_id_lists=[
            "<http://sts.amazonaws.com|sts.amazonaws.com>"],
        thumbprint_lists=["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"],
        url=eks_cluster.identities[0].oidcs[0].issuer)
    # create crossplaneadmin_role
    crossplane_admin_role = aws.iam.Role(
        "itplat_eks_crossplaneadmin_role",
        assume_role_policy=pulumi.Output.all(eks_oidc_provider.arn, eks_oidc_provider.id).apply(
            lambda args: json.dumps(
                {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Federated": args[0],
                            },
                            "Action": "sts:AssumeRoleWithWebIdentity",
                            "Condition": {
                                "StringLike": {f"{args[1]}:sub": "system:serviceaccount:crossplane-system:provider-aws-*"},
                            },
                        }
                    ],
                }
            ),
        name="itplat_eks_crossplaneadmin_role",
        tags={
            "clusterAccess": "itplat_eks_admin",
        },
        opts=pulumi.ResourceOptions(provider=providers['us-east-1']),
        )
    )
got me
Copy code
File "./aws_eks.py", line 116, in create
        assume_role_policy=pulumi.Output.all(eks_oidc_provider.arn, eks_oidc_provider.id).apply(
    TypeError: apply() got an unexpected keyword argument 'name'
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
and im lost again
b
@purple-plumber-90981
pulumi.Output.all
takes a list:
Copy code
pulumi.Output.all([eks_oidc_provider.arn, eks_oidc_provider.id])
note the square brackets
p
ah sweet, thanks
Copy code
assume_role_policy=pulumi.Output.all([eks_oidc_provider.arn, eks_oidc_provider.id]).apply(
    TypeError: apply() got an unexpected keyword argument 'name'
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
i think it must be the name keyword i pass aster assume_role_policy
b
Copy code
assume_role_policy=pulumi.Output.all(eks_oidc_provider.arn, eks_oidc_provider.id).apply(
            lambda args: json.dumps(
                {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Federated": args[0],
                            },
                            "Action": "sts:AssumeRoleWithWebIdentity",
                            "Condition": {
                                "StringLike": {f"{args[1]}:sub": "system:serviceaccount:crossplane-system:provider-aws-*"},
                            },
                        }
                    ],
                }
            ),
        name="itplat_eks_crossplaneadmin_role",
yea you're missing a closing parenthesis on the apply
p
it was in the wrong place, encapsulating the name= in the apply
fixed now… it was really late last night and i couldnt see it
b
ah yea it was behind options i see it. ok glad its working
p
Copy code
"StringLike": {f"{args[1]}:sub": "system:serviceaccount:crossplane-system:provider-aws-*"},
    IndexError: list index out of range
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
now i think the square brackets i added in teh output.all are borking it
trying without
yeah it works without the square brackets
b
probably just getting the languages confused, easy to do around here