Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full support for SSO resources #928

Merged
merged 11 commits into from
Dec 7, 2023
Merged

Conversation

mbbush
Copy link
Collaborator

@mbbush mbbush commented Oct 20, 2023

Description of your changes

  • Added 3 resources in the identitystore api group, which had been completely missing (these are the users and groups used for single sign on)
  • Added 3 missing resources to the ssoadmin api group, for full support, with external names as normalized as possible
  • Normalized the external names of two existing ssoadmin resources (PermissionSet and PermissionSetInlinePolicy)
  • Added cross references
  • Tested everything and verified it works.
  • Rebased on the latest main

This incorporates the changes in #920 and has been tested, so I'll close #920.

Fixes #536 (mostly)
Fixes #888
Closes #920

I have:

  • Run make check-diff test to validate the unit tests and generated code. Running the linter causes out-of-memory errors (it was using more than 50GB).

How has this code been tested

  • At commit aab2f0d, using uptest locally:
    I removed the manual-intervention annotations, substituted in my account-specific values, and ran make uptest with all the examples in this PR. All the tests passed (create, update, import, delete).

I did discover one breaking change from the switch to the no-fork provider. Details at #1013

Here's a snapshot of while true; do date; kubectl get managed; done from the time the first uptest step completed, showing everything synced and ready.

Wed Dec  6 12:24:33 PM PST 2023
NAME                                                                    READY   SYNCED   EXTERNAL-NAME                                 AGE
policy.iam.aws.upbound.io/ssoadmin-customer-managed-policy-attachment   True    True     ssoadmin-customer-managed-policy-attachment   29s
policy.iam.aws.upbound.io/ssoadmin-permissions-boundary-attachment      True    True     ssoadmin-permissions-boundary-attachment      29s

NAME                                                              READY   SYNCED   EXTERNAL-NAME                          AGE
groupmembership.identitystore.aws.upbound.io/example-membership   True    True     a4e884d8-c001-700a-f74a-a59b8050ed2e   29s

NAME                                                            READY   SYNCED   EXTERNAL-NAME                          AGE
group.identitystore.aws.upbound.io/example-group                True    True     74b8c4f8-60f1-7054-fe80-959d4c8a48c8   29s
group.identitystore.aws.upbound.io/example-membership           True    True     0478f4f8-3061-70cc-1fba-d9bc9bdebfe8   29s
group.identitystore.aws.upbound.io/ssoadmin-accountassignment   True    True     44d8e408-10a1-7076-de4e-eb531a66e562   29s

NAME                                                   READY   SYNCED   EXTERNAL-NAME                          AGE
user.identitystore.aws.upbound.io/example-membership   True    True     347884b8-3071-70ae-cf6d-236109cfcc9a   29s
user.identitystore.aws.upbound.io/example-user         True    True     74a85488-a021-70f6-cb73-f25adb5bb918   29s

NAME                                                                                 READY   SYNCED   EXTERNAL-NAME                                                                                                                                                                AGE
managedpolicyattachment.ssoadmin.aws.upbound.io/ssoadmin-managed-policy-attachment   True    True     arn:aws:iam::aws:policy/AlexaForBusinessDeviceSetup,arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-cbcf5801ea90aec7,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

NAME                                                                                                  READY   SYNCED   EXTERNAL-NAME                                    AGE
instanceaccesscontrolattributes.ssoadmin.aws.upbound.io/ssoadmin-instance-access-control-attributes   True    True     arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

NAME                                                                                                  READY   SYNCED   EXTERNAL-NAME                                                                                                                                                          AGE
customermanagedpolicyattachment.ssoadmin.aws.upbound.io/ssoadmin-customer-managed-policy-attachment   True    True     ssoadmin-customer-managed-policy-attachment,/,arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-4a48766aef623884,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

NAME                                                                                             READY   SYNCED   EXTERNAL-NAME         AGE
permissionsboundaryattachment.ssoadmin.aws.upbound.io/ssoadmin-permissions-boundary-attachment   True    True     ps-3d4a4ee185ba4972   29s

NAME                                                                                READY   SYNCED   EXTERNAL-NAME                                                                                                            AGE
permissionset.ssoadmin.aws.upbound.io/ssoadmin-accountassignment                    True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-a191ea70e4f281d0,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s
permissionset.ssoadmin.aws.upbound.io/ssoadmin-customer-managed-policy-attachment   True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-4a48766aef623884,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s
permissionset.ssoadmin.aws.upbound.io/ssoadmin-managed-policy-attachment            True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-cbcf5801ea90aec7,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s
permissionset.ssoadmin.aws.upbound.io/ssoadmin-permission-set                       True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-0dd46224d50dba40,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s
permissionset.ssoadmin.aws.upbound.io/ssoadmin-permission-set-inline-policy         True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-c85d97c97779dcc4,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s
permissionset.ssoadmin.aws.upbound.io/ssoadmin-permissions-boundary-attachment      True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-3d4a4ee185ba4972,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

NAME                                                                   READY   SYNCED   EXTERNAL-NAME                                                                                                                                                                                AGE
accountassignment.ssoadmin.aws.upbound.io/ssoadmin-accountassignment   True    True     44d8e408-10a1-7076-de4e-eb531a66e562,GROUP,374300188748,AWS_ACCOUNT,arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-a191ea70e4f281d0,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

NAME                                                                                      READY   SYNCED   EXTERNAL-NAME                                                                                                            AGE
permissionsetinlinepolicy.ssoadmin.aws.upbound.io/ssoadmin-permission-set-inline-policy   True    True     arn:aws:sso:::permissionSet/ssoins-7223ae4e1f24fd4c/ps-c85d97c97779dcc4,arn:aws:sso:::instance/ssoins-7223ae4e1f24fd4c   29s

How can someone else test this code?

AWS SSO can only be enabled in the "management account" of an organization. The easiest way to do this is in a standalone personal account. You can go to SSO identity center in the aws console to enable this, which will allow you to make your account into an organization. This will allow you to generate both an "identity store id" and an "sso instance arn" which are the two parameters you need to manually supply to the identitystore and ssoadmin example resources, respectively.

The example resources must be created in the same region as your SSO instance. I made my SSO instance in us-east-1, so that's the region I committed to all the example manifests.

AWS does not charge for any of these SSO resources, so there were no financial considerations to testing with a personal account.

Copy link
Collaborator

@turkenf turkenf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your effort @mbbush, I left a few comments. Could you please add screenshots/logs of all resources to the section on how it was tested after making the changes, as here?

config/ssoadmin/config.go Outdated Show resolved Hide resolved
examples/ssoadmin/managedpolicyattachment.yaml Outdated Show resolved Hide resolved
examples/ssoadmin/managedpolicyattachment.yaml Outdated Show resolved Hide resolved
examples/ssoadmin/permissionset.yaml Outdated Show resolved Hide resolved
config/ssoadmin/config.go Outdated Show resolved Hide resolved
examples/ssoadmin/permissionsetinlinepolicy.yaml Outdated Show resolved Hide resolved
p.AddResourceConfigurator("aws_identitystore_group", func(r *config.Resource) {
// Display name is required by terraform, and while it's not part of the external name or terraform id, it is
// how the group is displayed, and it's immutable.
r.ExternalName.IdentifierFields = append(r.ExternalName.IdentifierFields, "display_name")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an easy, idiomatic golang way to append this only if it's not already present? In e.g. python I'd probably do it by converting to a set and then back to a list. Or maybe it's not worth worrying about since this happens at generation time.

Or maybe it's better to leave this off? I added it in an effort to get the field marked as required in the generated schema, but subsequently I realized that many fields which are actually required by terraform are not marked as required in the schema, but are instead validated at the kubernetes level using slightly more sophisticated logic that takes into account initProvider and managementPolicies.

Conceptually it makes sense that the display name would be an identifier field. But it's not necessary to mark it as such in order for the managed resource to work properly.

@mbbush
Copy link
Collaborator Author

mbbush commented Oct 24, 2023

@turkenf This is ready for another look.

@mbbush mbbush requested a review from turkenf October 24, 2023 23:56
@mbbush
Copy link
Collaborator Author

mbbush commented Oct 25, 2023

@turkenf Ready for your review again. Is there anything else this needs before merging? I'm hoping to get this in the October release so I can start using these resources next week at work.

@turkenf
Copy link
Collaborator

turkenf commented Oct 26, 2023

I am closing and reopening PR to run license/cla again.

@turkenf turkenf closed this Oct 26, 2023
@turkenf turkenf reopened this Oct 26, 2023
@@ -95,6 +95,7 @@ spec:
description: Key-value map of resource tags.
type: object
required:
- instanceArn
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. It's a consequence of normalizing the external name for this resource.

From a practical point of view, this will only affect Observe only resources, because this parameter was always required by terraform, and therefore included in the kubernetes validation. If I make it no longer required, if this is missing, and the external name annotation is set to the permission set id (the normalized external name I chose), crossplane will be unable to construct the terraform id, which contains the instance arn, so it won't be able to import the existing resource.

I could remove it from the IdentifierFields in the external name configuration, which would avoid the breaking schema change, but it feels like it really should be required, especially since omitting it will cause imports to fail.

Another option would be to change the external name to include the instance arn (perhaps by changing it from the short id of the permission set to the full ARN of the permission set, which embeds the id of the instance, from which the instance arn can be derived). But that seems like sacrificing the long-term user experience in order to avoid a minor breaking api schema change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this removed from IdentifierFields in the external name config, the following examples still work:

  • A resource that specifies all the fields in spec.forProvider with default management policies
  • A resource that doesn't include this field in spec.forProvider, but does include it in spec.initProvider, with default management policies (this field is immutable, so there's really no reason someone would want to put it in spec.initProvider)
  • A resource with only the Observe management policy, that specifies the external name annotation and populates this field to the correct value in spec.forProvider

This doesn't work:

  • A resource with only the Observe management policy, that specifies the external name annotation, but doesn't populate this field. This passes the kubernetes validator, but fails at runtime because it can't construct the terraform id.

Copy link
Collaborator Author

@mbbush mbbush Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of a handful of ways forward here:

  1. Normalize the external name to just the provider-generated part of the ARN, and leave instanceArn as an identifier field. This is the approach the existing PR takes, and if this was a new resource, as opposed to an update to an existing one, this seems like it would be the clear winner.
  2. Normalize the external name, but don't specify instanceArn as an identifier field. This will cause an error for Observe resources as mentioned above.
  3. Use the permission set ARN as the external name (as opposed to the current external name which matches the terraform id: ${permission_set_arn},${instance_arn}). This could be done without specifying instanceArn as an identifier field because the instance arn can be derived from the permission set arn.
  4. Leave the current external name which matches the terraform id. This would certainly be the smallest change, but it's also a fairly cumbersome external name to have to use.

One factor that seems relevant to deciding is what happens to existing managed resources during a provider upgrade, when the external name config changes. I'm hoping that as long as the resources were ready, crossplane would simply reconstruct the external name from the terraform id, using the new logic, which would provide the correct result, but I'm not sure quite how best to test this. Would it be sufficient to take a MR that has become ready and simply edit its external-name annotation to the old format? Would there be other edits I should do at the same time? Or is it necessary to do a "full" test, where I actually do a provider upgrade on my test cluster?

// SSO Managed Policy Attachments can be imported using the name, path, permission_set_arn, and instance_arn separated by a comma (,)
// Example: TestPolicy,/,arn:aws:sso:::permissionSet/ssoins-2938j0x8920sbj72/ps-80383020jr9302rk,arn:aws:sso:::instance/ssoins-2938j0x8920sbj72
// This can't really be normalized.
"aws_ssoadmin_customer_managed_policy_attachment": config.TemplatedStringAsIdentifier("", "{{ (index .parameters.customer_managed_policy_reference 0).name }},{{ (index .parameters.customer_managed_policy_reference 0).path }},{{ .parameters.permission_set_arn }},{{ .parameters.instance_arn }}"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This results in the crossplane external name matching the terraform id, which is quite long, but it occurs to me that perhaps it doesn't need to?

This is quite possibly a bad idea, but it's not immediately obvious to me why we couldn't just have the external-name match metadata.name, and construct the terraform id by ignoring the external name and applying the template to the parameters.

@mbbush
Copy link
Collaborator Author

mbbush commented Nov 7, 2023

@turkenf Anything else I can do to get this merged?

@turkenf
Copy link
Collaborator

turkenf commented Nov 7, 2023

@mbbush, there is nothing you can do right now, it is waiting for the team to review, it will be reviewed as soon as possible, thank you.

@sergenyalcin
Copy link
Collaborator

@mbbush Thank you for this contribution. I reviewed the PR. Let me talk about two parts of this. The first part is adding/configuring new resources. I think this part is ready for merge. The second part is changing the external name configurations for two resources. I am concerned about the breaking API changes. We do not want to introduce any breaking API changes in minor versions.

It seems that for the PermissionSet resource, we are introducing a new required field and deleting the X-Rules. As you mentioned, this is a breaking change. Also, you are correct that this was a required field in behavior because of the X-Rules.

In the mid/long-term, we want to switch to XValidation-Rules where possible so that fields behave as required. So, ideally, we want to leave no required fields in the schemas and ensure that the relevant fields act as mandatory with XValidation-Rules.

For the PermissionSetInlinePolicy, do we change the external-name syntax? If this is the case, this will also be a breaking change.

In short, if we address these normalizations without breaking changes (API or external name syntax), I think the normalization part will be okay for the minor version. However, if this is not the case and if we need some breaking changes for normalization, I will suggest separating the PR into two parts. And merging the adding new resources part here (for six resources). Then, we may consider handling the remaining external name normalizations in another PR for the major version releases.

@mbbush
Copy link
Collaborator Author

mbbush commented Dec 6, 2023

Thanks for the review @sergenyalcin!

I've rebased on the latest main, removed the normalization of PermissionSet, discarded all my codegen commits and regnerated.

The external name for PermissionSetInlinePolicy is changing, from arn:aws:sso:::permissionSet/ssoins-2938j0x8920sbj72/ps-80383020jr9302rk,arn:aws:sso:::instance/ssoins-2938j0x8920sbj72 to just ps-80383020jr9302rk, however I believe that this is not actually a breaking change. As is clear from the lack of a diff in the generated code, the schema doesn't change, as in both cases the same two parameters are set as identifier fields. An existing managed resource with the old external name won't lose its reference to the terraform id, since GetIDFn will still create the desired id (in both the new and old versions the only relevant inputs were in spec.forProvider). It will just have its external name updated to the new value the next time GetExternalNameFn runs.

The only thing I can think of possibly breaking as a result would be a composition or gitops manifest that was constructing the external name for an Observe resource and specifying it in the old format. Are we saying that the format of external names (which we don't actually document anywhere but the source code) is a part of the public API that we're committed to not changing? externalname.go is full of // TODO normalize when testing comments, so I assumed it was considered unstable.

If you still think it's important to not change the external name for PermissionSetInlinePolicy, I can pull that change out too. I would really like to get this included in the December release. (I originally wanted to get it in the October release!)

I'll rerun the tests tomorrow and edit the top comment with results.

@sergenyalcin
Copy link
Collaborator

@mbbush

Are we saying that the format of external names (which we don't actually document anywhere but the source code) is a part of the public API that we're committed to not changing?

Yes, I was trying to say that the external names are also part of the public API. The API normalization is important in the context of the provider quality. Before, we observed some issues with the breaking external name changes that could not be handled and caused a few problems. So, external names are part of the public API.

If it is also okay for you, I think we may consider removing the PermissionSetInlinePolicy change from this PR to preserve the public API. As I mentioned in my previous message, we will have a major version bump, and on this version, we will have a chance to include these types of breaking API changes. If you want, please open a new PR for these resources, and we can include these normalizations on the major bump.

Thank you for bearing with me throughout this PR.

@mbbush
Copy link
Collaborator Author

mbbush commented Dec 7, 2023

@sergenyalcin this is rebased, tested, and ready for review with the changes you requested.

Copy link
Collaborator

@sergenyalcin sergenyalcin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mbbush for this valuable contribution, LGTM!

@mbbush
Copy link
Collaborator Author

mbbush commented Dec 7, 2023

Great, thanks. I don't have the write access to merge it myself, so someone else will have to click the button.

@turkenf turkenf merged commit 467e1e9 into crossplane-contrib:main Dec 7, 2023
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants