diff --git a/okta/app.go b/okta/app.go index a3624bad4..86fe4a9e3 100644 --- a/okta/app.go +++ b/okta/app.go @@ -719,3 +719,38 @@ func setAppUsersIDsAndGroupsIDs(ctx context.Context, d *schema.ResourceData, cli } return nil } + +// fetchAppKeys returns the keys from `/api/v1/apps/${applicationId}/credentials/keys` for a given app. Not all fields on the JsonWebKey +// will be set, please consult the documentation (https://developer.okta.com/docs/reference/api/apps/#list-key-credentials-for-application) +// for more information. +func fetchAppKeys(ctx context.Context, m interface{}, appID string) ([]*okta.JsonWebKey, error) { + keys, _, err := getOktaClientFromMetadata(m).Application.ListApplicationKeys(ctx, appID) + + if err != nil { + return nil, err + } + + return keys, nil +} + +// setAppKeys sets the JWKs return by fetchAppKeys on the given resource. +func setAppKeys(d *schema.ResourceData, keys []*okta.JsonWebKey) error { + arr := make([]map[string]interface{}, len(keys)) + + for i, key := range keys { + arr[i] = map[string]interface{}{ + "kid": key.Kid, + "kty": key.Kty, + "use": key.Use, + "created": key.Created.String(), + "last_updated": key.LastUpdated.String(), + "expires_at": key.ExpiresAt.String(), + "e": key.E, + "n": key.N, + "x5c": key.X5c, + "x5t_s256": key.X5tS256, + } + } + + return d.Set("keys", arr) +} diff --git a/okta/resource_okta_app_saml.go b/okta/resource_okta_app_saml.go index 136b21469..448dc6428 100644 --- a/okta/resource_okta_app_saml.go +++ b/okta/resource_okta_app_saml.go @@ -79,6 +79,66 @@ func resourceAppSaml() *schema.Resource { ValidateDiagFunc: intBetween(2, 10), Description: "Number of years the certificate is valid.", }, + "keys": { + Type: schema.TypeList, + Description: "Application keys", + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kid": { + Type: schema.TypeString, + Description: "Key ID", + Computed: true, + }, + "kty": { + Type: schema.TypeString, + Description: "Key type", + Computed: true, + }, + "use": { + Type: schema.TypeString, + Description: "Acceptable usage of the certificate", + Computed: true, + }, + "created": { + Type: schema.TypeString, + Description: "Created date", + Computed: true, + }, + "last_updated": { + Type: schema.TypeString, + Description: "Last updated date", + Computed: true, + }, + "expires_at": { + Type: schema.TypeString, + Description: "Expiration date", + Computed: true, + }, + "e": { + Type: schema.TypeString, + Description: "RSA exponent", + Computed: true, + }, + "n": { + Type: schema.TypeString, + Description: "RSA modulus", + Computed: true, + }, + "x5c": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "X.509 Certificate Chain", + Computed: true, + }, + "x5t_s256": { + Type: schema.TypeString, + Description: "X.509 certificate SHA-256 thumbprint", + Computed: true, + }, + }, + }, + }, "metadata": { Type: schema.TypeString, Description: "SAML xml metadata payload", @@ -436,6 +496,17 @@ func resourceAppSamlRead(ctx context.Context, d *schema.ResourceData, m interfac _ = d.Set("entity_key", key) _ = d.Set("certificate", desc.KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data) } + + keys, err := fetchAppKeys(ctx, m, app.Id) + + if err != nil { + return diag.Errorf("failed to load existing keys for SAML application: %f", err) + } + + if err := setAppKeys(d, keys); err != nil { + return diag.Errorf("failed to set Application Credential Key Values: %v", err) + } + appRead(d, app.Name, app.Status, app.SignOnMode, app.Label, app.Accessibility, app.Visibility, app.Settings.Notes) if app.SignOnMode == "SAML_1_1" { _ = d.Set("saml_version", saml11) @@ -644,6 +715,7 @@ func tryCreateCertificate(ctx context.Context, d *schema.ResourceData, m interfa // Set ID and the read done at the end of update and create will do the GET on metadata _ = d.Set("key_id", key.Kid) } + return nil } diff --git a/okta/resource_okta_app_saml_test.go b/okta/resource_okta_app_saml_test.go index 3c0bc6e5a..9f12bb7fe 100644 --- a/okta/resource_okta_app_saml_test.go +++ b/okta/resource_okta_app_saml_test.go @@ -261,6 +261,7 @@ func TestAccAppSaml_userGroups(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "label", buildResourceName(ri)), resource.TestCheckResourceAttr(resourceName, "status", statusActive), resource.TestCheckResourceAttrSet(resourceName, "key_id"), + resource.TestCheckResourceAttrSet(resourceName, "keys"), resource.TestCheckResourceAttr(resourceName, "groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "users.#", "1"), ), diff --git a/website/docs/r/app_saml.html.markdown b/website/docs/r/app_saml.html.markdown index 7040d58b4..fd9e6a763 100644 --- a/website/docs/r/app_saml.html.markdown +++ b/website/docs/r/app_saml.html.markdown @@ -282,6 +282,28 @@ The following arguments are supported: - `key_name` - Certificate name. This modulates the rotation of keys. New name == new key. +- `keys` - An array of all key credentials for the application. Format of each entry is as follows: + + - `kid` - Key ID. + + - `kty` - Identifies the cryptographic algorithm family used with the key. + + - `use` - Intended use of the public key. + + - `created` - Date created. + + - `last_updated` - Date the key was last updated. + + - `expires_at` - Date the key expires. + + - `e` - RSA exponent. + + - `n` - RSA modulus. + + - `x5c` - X.509 certificate chain. + + - `x5t_s256` - X.509 certificate SHA-256 thumbprint. + - `certificate` - The raw signing certificate. - `metadata` - The raw SAML metadata in XML.