From 73c723fecddce10680ac7319ae78b015011d1762 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 4 Feb 2024 17:50:34 +0200 Subject: [PATCH 01/25] creation works, but state inconsistent --- .gitignore | 1 + codefresh/cfclient/idp.go | 94 +++++++--- codefresh/internal/schemautil/validation.go | 2 +- codefresh/provider.go | 1 + codefresh/resource_idp.go | 191 ++++++++++++++++++++ codefresh/resource_pipeline_test.go | 4 +- main.go | 1 + 7 files changed, 265 insertions(+), 29 deletions(-) create mode 100644 codefresh/resource_idp.go diff --git a/.gitignore b/.gitignore index 78aae934..ad6a40bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ terraform-provider-codefresh dist/ .vscode/ +**/__debug* **/.terraform **/terraform.tfstate diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 928a034c..3b43d22f 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -3,35 +3,77 @@ package cfclient import ( "errors" "fmt" + "log" ) type IDP struct { - Access_token string `json:"access_token,omitempty"` - Accounts []string `json:"accounts,omitempty"` - ApiHost string `json:"apiHost,omitempty"` - ApiPathPrefix string `json:"apiPathPrefix,omitempty"` - ApiURL string `json:"apiURL,omitempty"` - AppId string `json:"appId,omitempty"` - AuthURL string `json:"authURL,omitempty"` - ClientHost string `json:"clientHost,omitempty"` - ClientId string `json:"clientId,omitempty"` - ClientName string `json:"clientName,omitempty"` - ClientSecret string `json:"clientSecret,omitempty"` - ClientType string `json:"clientType,omitempty"` - CookieIv string `json:"cookieIv,omitempty"` - CookieKey string `json:"cookieKey,omitempty"` - DisplayName string `json:"displayName,omitempty"` - ID string `json:"_id,omitempty"` - IDPLoginUrl string `json:"IDPLoginUrl,omitempty"` - LoginUrl string `json:"loginUrl,omitempty"` - RedirectUiUrl string `json:"redirectUiUrl,omitempty"` - RedirectUrl string `json:"redirectUrl,omitempty"` - RefreshTokenURL string `json:"refreshTokenURL,omitempty"` - Scopes []string `json:"scopes,omitempty"` - Tenant string `json:"tenant,omitempty"` - TokenSecret string `json:"tokenSecret,omitempty"` - TokenURL string `json:"tokenURL,omitempty"` - UserProfileURL string `json:"userProfileURL,omitempty"` + ID string `json:"_id,omitempty"` + Access_token string `json:"access_token,omitempty"` + Accounts []string `json:"accounts,omitempty"` + ClientName string `json:"clientName,omitempty"` // IDP name + ClientType string `json:"clientType,omitempty"` // IDP type + DisplayName string `json:"displayName,omitempty"` + LoginUrl string `json:"loginUrl,omitempty"` // Login url in Codefresh + RedirectUiUrl string `json:"redirectUiUrl,omitempty"` // Redicrect url Codefresh UI + RedirectUrl string `json:"redirectUrl,omitempty"` + ClientId string `json:"clientId,omitempty"` // All providers (base) + ClientSecret string `json:"clientSecret,omitempty"` // All providers (base) + ApiHost string `json:"apiHost,omitempty"` // GitHub + ApiPathPrefix string `json:"apiPathPrefix,omitempty"` // Github + // Bitbucket, Gitlab + ApiURL string `json:"apiURL,omitempty"` + // Azure, Okta, onelogin,saml + AppId string `json:"appId,omitempty"` + // Github, Gitlab + AuthURL string `json:"authURL,omitempty"` + // saml, okta, onelogin, auth0, azure, google, google-cloud-sr + ClientHost string `json:"clientHost,omitempty"` + // Azure + CookieIv string `json:"cookieIv,omitempty"` + // Azure + CookieKey string `json:"cookieKey,omitempty"` + // Azure + IDPLoginUrl string `json:"IDPLoginUrl,omitempty"` + // Bitbucket + RefreshTokenURL string `json:"refreshTokenURL,omitempty"` + // Multiple - computed + Scopes []string `json:"scopes,omitempty"` + // Azure + Tenant string `json:"tenant,omitempty"` + TokenSecret string `json:"tokenSecret,omitempty"` + // Okta, Bitbucket, GitHub, Keycloak + TokenURL string `json:"tokenURL,omitempty"` + // Github, Gitlab + UserProfileURL string `json:"userProfileURL,omitempty"` +} + +func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { + + body, err := EncodeToJSON(idp) + + if err != nil { + return nil, err + } + opts := RequestOptions{ + Path: "/admin/idp", + Method: "POST", + Body: body, + } + + resp, err := client.RequestAPI(&opts) + + if err != nil { + log.Printf("[DEBUG] Call to API for IDP creation failed with Error = %v for Body %v", err, body) + return nil, err + } + + var respIDP IDP + err = DecodeResponseInto(resp, &respIDP) + if err != nil { + return nil, err + } + + return &respIDP, nil } // get all idps diff --git a/codefresh/internal/schemautil/validation.go b/codefresh/internal/schemautil/validation.go index 263ae1fa..3cc188bd 100644 --- a/codefresh/internal/schemautil/validation.go +++ b/codefresh/internal/schemautil/validation.go @@ -89,4 +89,4 @@ func (o *ValidationOptions) setSummary(summary string) *ValidationOptions { func (o *ValidationOptions) setDetailFormat(detailFormat string) *ValidationOptions { o.detailFormat = detailFormat return o -} \ No newline at end of file +} diff --git a/codefresh/provider.go b/codefresh/provider.go index c79e8086..6d456a51 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -68,6 +68,7 @@ func Provider() *schema.Provider { "codefresh_user": resourceUser(), "codefresh_team": resourceTeam(), "codefresh_abac_rules": resourceGitopsAbacRule(), + "codefresh_idp": resourceIdp(), }, ConfigureFunc: configureProvider, } diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go new file mode 100644 index 00000000..73e6611a --- /dev/null +++ b/codefresh/resource_idp.go @@ -0,0 +1,191 @@ +package codefresh + +import ( + "log" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + + +func resourceIdp() *schema.Resource { + return &schema.Resource{ + Description: "Identity providers used in Codefresh for user authentication.", + Create: resourceIDPCreate, + Read: resourceIDPRead, + Update: resourceIDPUpdate, + Delete: resourceIDPDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "display_name": { + Description: "The display name for the IDP.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "Name of the IDP, will be generated if not set", + Type: schema.TypeString, + Optional: true, + }, + "client_type": { + Description: "Type of the IDP", + Type: schema.TypeString, + Computed: true, + }, + "redirect_url": { + Description: "API Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "redirect_ui_url": { + Description: "UI Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "login_url": { + Type: schema.TypeString, + Computed: true, + }, + "github": { + Description: "Settings for GitHub IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "authentication_url": { + Type: schema.TypeString, + Optional: true, + Default: "https://github.com/login/oauth/authorize", + }, + "token_url": { + Type: schema.TypeString, + Optional: true, + Default: "https://github.com/login/oauth/access_token", + }, + "user_profile_url": { + Type: schema.TypeString, + Optional: true, + Default: "https://api.github.com/user", + }, + "api_host": { + Type: schema.TypeString, + Optional: true, + Default: "api.github.com", + }, + "api_path_prefix": { + Type: schema.TypeString, + Optional: true, + Default: "/", + }, + }, + }, + }, + }, + } +} + +func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + + resp, err := client.CreateIDP(mapResourceToIDP(d)) + + if err != nil { + log.Printf("[DEBUG] Error while creating idp. Error = %v", err) + return err + } + + d.SetId(resp.ID) + return resourceIDPRead(d, meta) +} + +func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + idpID := d.Id() + + cfClientIDP, err := client.GetIdpByID(idpID) + + if err != nil { + log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) + return err + } + + err = mapIDPToResource(*cfClientIDP, d) + + if err != nil { + log.Printf("[DEBUG] Error while getting mapping response to IDP object. Error = %v", err) + return err + } + + return nil +} + +func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { + d.SetId(cfClientIDP.ID) + d.Set("display_name", cfClientIDP.DisplayName) + d.Set("name", cfClientIDP.ClientName) + d.Set("redirect_url", cfClientIDP.RedirectUrl) + d.Set("redirect_ui_url", cfClientIDP.RedirectUiUrl) + d.Set("login_url", cfClientIDP.LoginUrl) + + if cfClientIDP.ClientType == "github" { + d.Set("github.0.client_id", cfClientIDP.ClientId) + d.Set("github.0.client_secret", cfClientIDP.ClientSecret) + d.Set("github.0.authentication_url", cfClientIDP.AuthURL) + d.Set("github.0.token_url", cfClientIDP.TokenURL) + d.Set("github.0.user_profile_url", cfClientIDP.UserProfileURL) + d.Set("github.0.api_host", cfClientIDP.ApiHost) + d.Set("github.0.api_path_prefix", cfClientIDP.ApiPathPrefix) + } + + return nil +} + +func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { + cfClientIDP := &cfclient.IDP{ + ID: d.Id(), + DisplayName: d.Get("display_name").(string), + ClientName: d.Get("name").(string), + RedirectUrl: d.Get("redirect_url").(string), + RedirectUiUrl: d.Get("redirect_ui_url").(string), + LoginUrl: d.Get("login_url").(string), + } + + // client_type - Set dynamically + if _, ok := d.GetOk("github"); ok { + cfClientIDP.ClientType = "github" + cfClientIDP.ClientId = d.Get("github.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) + cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) + cfClientIDP.TokenURL = d.Get("github.0.token_url").(string) + cfClientIDP.UserProfileURL = d.Get("github.0.user_profile_url").(string) + cfClientIDP.ApiHost = d.Get("github.0.api_host").(string) + cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) + } + + return cfClientIDP +} + + + diff --git a/codefresh/resource_pipeline_test.go b/codefresh/resource_pipeline_test.go index 4bfcfdf9..07dff4ad 100644 --- a/codefresh/resource_pipeline_test.go +++ b/codefresh/resource_pipeline_test.go @@ -532,8 +532,8 @@ func TestAccCodefreshPipeline_CronTriggersInvalid(t *testing.T) { var pipeline cfclient.Pipeline resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, Steps: []resource.TestStep{ { Config: testAccCodefreshPipelineBasicConfigCronTriggers( diff --git a/main.go b/main.go index ac8ab335..39ca6237 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ func main() { providerAddr = codefresh.DEFAULT_CODEFRESH_PLUGIN_ADDR } plugin.Serve(&plugin.ServeOpts{ + ProviderAddr: "codefresh-io/codefresh", // Required for debug attaching ProviderFunc: codefresh.Provider, Debug: debugMode, }) From f1c0ac470f7a83c4c8bc2f67241a3680568cfae2 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 4 Feb 2024 17:59:30 +0200 Subject: [PATCH 02/25] creation fully works --- codefresh/resource_idp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 73e6611a..e354979a 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -27,7 +27,7 @@ func resourceIdp() *schema.Resource { "name": { Description: "Name of the IDP, will be generated if not set", Type: schema.TypeString, - Optional: true, + Computed: true, }, "client_type": { Description: "Type of the IDP", From 92bce15c04f477cd1ab8b97c1562cb22a92a5430 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 5 Feb 2024 18:19:40 +0200 Subject: [PATCH 03/25] crud works with list not picking drift caveat --- codefresh/cfclient/idp.go | 47 ++++++++++++++++++++++++++++ codefresh/resource_idp.go | 65 +++++++++++++++++++++++++++++++++++---- 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 3b43d22f..84f3b097 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "log" + "net/url" ) type IDP struct { @@ -76,6 +77,52 @@ func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { return &respIDP, nil } +func (client *Client) UpdateIDP(idp *IDP) (*IDP, error) { + + body, err := EncodeToJSON(idp) + + if err != nil { + return nil, err + } + opts := RequestOptions{ + Path: "/admin/idp", + Method: "PUT", + Body: body, + } + + resp, err := client.RequestAPI(&opts) + + if err != nil { + log.Printf("[DEBUG] Call to API for IDP update failed with Error = %v for Body %v", err, body) + return nil, err + } + + var respIDP IDP + err = DecodeResponseInto(resp, &respIDP) + if err != nil { + return nil, err + } + + return &respIDP, nil +} + +func (client *Client) DeleteIDP(id string) error { + + fullPath := fmt.Sprintf("/admin/idp/%s", url.PathEscape(id)) + opts := RequestOptions{ + Path: fullPath, + Method: "DELETE", + } + + _, err := client.RequestAPI(&opts) + + if err != nil { + return err + } + + return nil +} + // get all idps func (client *Client) GetIDPs() (*[]IDP, error) { fullPath := "/admin/idp" diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index e354979a..6051aec9 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -1,6 +1,7 @@ package codefresh import ( + "fmt" "log" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" @@ -28,6 +29,7 @@ func resourceIdp() *schema.Resource { Description: "Name of the IDP, will be generated if not set", Type: schema.TypeString, Computed: true, + Optional: true, }, "client_type": { Description: "Type of the IDP", @@ -48,11 +50,19 @@ func resourceIdp() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "config_hash": { + Type: schema.TypeString, + Computed: true, + }, "github": { Description: "Settings for GitHub IDP", Type: schema.TypeList, Optional: true, MaxItems: 1, + DiffSuppressOnRefresh: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return true + }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { @@ -119,8 +129,12 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { cfClientIDP, err := client.GetIdpByID(idpID) if err != nil { + if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { + return nil + } log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) return err + } err = mapIDPToResource(*cfClientIDP, d) @@ -134,11 +148,28 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { } func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + err := client.DeleteIDP(d.Id()) + + if err != nil { + log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) + return err + } return nil } func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + + client := meta.(*cfclient.Client) + + _, err := client.UpdateIDP(mapResourceToIDP(d)) + + if err != nil { + log.Printf("[DEBUG] Error while updating idp. Error = %v", err) + return err + } + + return resourceIDPRead(d, meta) } func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { @@ -148,6 +179,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("redirect_url", cfClientIDP.RedirectUrl) d.Set("redirect_ui_url", cfClientIDP.RedirectUiUrl) d.Set("login_url", cfClientIDP.LoginUrl) + d.Set("client_type", cfClientIDP.ClientType) if cfClientIDP.ClientType == "github" { d.Set("github.0.client_id", cfClientIDP.ClientId) @@ -156,7 +188,20 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("github.0.token_url", cfClientIDP.TokenURL) d.Set("github.0.user_profile_url", cfClientIDP.UserProfileURL) d.Set("github.0.api_host", cfClientIDP.ApiHost) - d.Set("github.0.api_path_prefix", cfClientIDP.ApiPathPrefix) + d.Set("github.0.api_path_prefix", cfClientIDP.ApiPathPrefix) + + // mapSlice := []map[string]interface{}{} + // map1 := map[string]interface{}{ + // "client_id": cfClientIDP.ClientId, + // "client_secret": cfClientIDP.ClientSecret, + // "authentication_url": cfClientIDP.AuthURL, + // "token_url": cfClientIDP.TokenURL, + // "user_profile_url": cfClientIDP.UserProfileURL, + // "api_host": cfClientIDP.ApiHost, + // "api_path_prefix": cfClientIDP.ApiPathPrefix, + // } + // mapSlice = append(mapSlice, map1) + // d.Set("github",mapSlice) } return nil @@ -172,7 +217,6 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { LoginUrl: d.Get("login_url").(string), } - // client_type - Set dynamically if _, ok := d.GetOk("github"); ok { cfClientIDP.ClientType = "github" cfClientIDP.ClientId = d.Get("github.0.client_id").(string) @@ -184,8 +228,17 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) } + // if idpAttributes, ok := d.GetOk("github"); ok { + // ghAttributes := idpAttributes.(*schema.Set).List()[0].(map[string]interface{}) + // cfClientIDP.ClientType = "github" + // cfClientIDP.ClientId = ghAttributes["client_id"].(string) + // cfClientIDP.ClientSecret = ghAttributes["client_secret"].(string) + // cfClientIDP.AuthURL = ghAttributes["authentication_url"].(string) + // cfClientIDP.TokenURL = ghAttributes["token_url"].(string) + // cfClientIDP.UserProfileURL = ghAttributes["user_profile_url"].(string) + // cfClientIDP.ApiHost = ghAttributes["api_host"].(string) + // cfClientIDP.ApiPathPrefix = ghAttributes["api_path_prefix"].(string) + // } + return cfClientIDP } - - - From 2a0816b72514c4ebcf32f2eeb7b858c179d05866 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 5 Feb 2024 19:01:54 +0200 Subject: [PATCH 04/25] crud works with list not picking drift caveat --- codefresh/resource_idp.go | 58 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 6051aec9..998413a2 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -34,6 +34,7 @@ func resourceIdp() *schema.Resource { "client_type": { Description: "Type of the IDP", Type: schema.TypeString, + ForceNew: true, Computed: true, }, "redirect_url": { @@ -59,10 +60,7 @@ func resourceIdp() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - DiffSuppressOnRefresh: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return true - }, + ExactlyOneOf: []string{"gitlab"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { @@ -102,6 +100,41 @@ func resourceIdp() *schema.Resource { }, }, }, + "gitlab": { + Description: "Settings for GitLab IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"github"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "authentication_url": { + Type: schema.TypeString, + Optional: true, + Default: "https://gitlab.com", + }, + "user_profile_url": { + Type: schema.TypeString, + Optional: true, + Default: "https://api.github.com/user", + }, + "api_url": { + Type: schema.TypeString, + Optional: true, + Default: "api.github.com", + }, + }, + }, + }, }, } } @@ -204,6 +237,14 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { // d.Set("github",mapSlice) } + if cfClientIDP.ClientType == "gitlab" { + d.Set("gitlab.0.client_id", cfClientIDP.ClientId) + d.Set("gitlab.0.client_secret", cfClientIDP.ClientSecret) + d.Set("gitlab.0.authentication_url", cfClientIDP.AuthURL) + d.Set("gitlab.0.user_profile_url", cfClientIDP.UserProfileURL) + d.Set("gitlab.0.api_url", cfClientIDP.ApiURL) + } + return nil } @@ -228,6 +269,15 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) } + if _, ok := d.GetOk("gitlab"); ok { + cfClientIDP.ClientType = "gitlab" + cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) + cfClientIDP.AuthURL = d.Get("gitlab.0.authentication_url").(string) + cfClientIDP.UserProfileURL = d.Get("gitlab.0.user_profile_url").(string) + cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) + } + // if idpAttributes, ok := d.GetOk("github"); ok { // ghAttributes := idpAttributes.(*schema.Set).List()[0].(map[string]interface{}) // cfClientIDP.ClientType = "github" From 41a104d9cc6f3ab9e213c5054d55d23aca8d93ce Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 6 Feb 2024 10:11:27 +0200 Subject: [PATCH 05/25] recreate if deleted --- codefresh/resource_idp.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 998413a2..396093cb 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -3,9 +3,11 @@ package codefresh import ( "fmt" "log" + //"context" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + //"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" ) @@ -19,6 +21,14 @@ func resourceIdp() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + // CustomizeDiff: customdiff.All( + // customdiff.ForceNewIf("client_type", func(_ context.Context, diff *schema.ResourceDiff, _ any) bool { + // _, newStatus := diff.GetChange("client_type") + // fmt.Print(newStatus) + // return true + // //return d.Get("status").(string) != "pending" && d.HasChange("email") + // }), + // ), Schema: map[string]*schema.Schema{ "display_name": { Description: "The display name for the IDP.", @@ -34,7 +44,6 @@ func resourceIdp() *schema.Resource { "client_type": { Description: "Type of the IDP", Type: schema.TypeString, - ForceNew: true, Computed: true, }, "redirect_url": { @@ -163,6 +172,7 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { + d.SetId("") return nil } log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) @@ -258,8 +268,16 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { LoginUrl: d.Get("login_url").(string), } + if d.Get("client_type") != nil { + cfClientIDP.ClientType = d.Get("client_type").(string) + } else { + cfClientIDP.ClientType = "" + } + if _, ok := d.GetOk("github"); ok { - cfClientIDP.ClientType = "github" + if cfClientIDP.ClientType == "" { + cfClientIDP.ClientType = "github" + } cfClientIDP.ClientId = d.Get("github.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) @@ -270,6 +288,9 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { } if _, ok := d.GetOk("gitlab"); ok { + if cfClientIDP.ClientType == "" { + cfClientIDP.ClientType = "gitlab" + } cfClientIDP.ClientType = "gitlab" cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) From 39822294d3fb5017f2f36e67fb968b4ad1de5c00 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 6 Feb 2024 19:41:01 +0200 Subject: [PATCH 06/25] replace works and drift is detected --- codefresh/resource_idp.go | 126 +++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 49 deletions(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 396093cb..1847c1dc 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -3,11 +3,12 @@ package codefresh import ( "fmt" "log" - //"context" + "errors" + "context" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - //"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" ) @@ -21,14 +22,23 @@ func resourceIdp() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - // CustomizeDiff: customdiff.All( - // customdiff.ForceNewIf("client_type", func(_ context.Context, diff *schema.ResourceDiff, _ any) bool { - // _, newStatus := diff.GetChange("client_type") - // fmt.Print(newStatus) - // return true - // //return d.Get("status").(string) != "pending" && d.HasChange("email") - // }), - // ), + CustomizeDiff: customdiff.All( + // Recreate idp if the type has changed - we cannot simply do ForceNew on client_type as it is computed + customdiff.ForceNewIf("client_type", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool { + clientTypeInState := d.Get("client_type").(string) + attributesForIdpTypeInState := d.Get(clientTypeInState) + // If there is a different type of idp in the state, the idp needs to be recreated + if attributesForIdpTypeInState == nil { + d.SetNewComputed("client_type") + return true + } else if len(attributesForIdpTypeInState.([]interface{})) < 1 { + d.SetNewComputed("client_type") + return true + } else { + return false + } + }), + ), Schema: map[string]*schema.Schema{ "display_name": { Description: "The display name for the IDP.", @@ -42,9 +52,10 @@ func resourceIdp() *schema.Resource { Optional: true, }, "client_type": { - Description: "Type of the IDP", + Description: "Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc)", Type: schema.TypeString, Computed: true, + ForceNew: true, }, "redirect_url": { Description: "API Callback url for the identity provider", @@ -81,6 +92,11 @@ func resourceIdp() *schema.Resource { Required: true, Sensitive: true, }, + "client_secret_encrypted": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "authentication_url": { Type: schema.TypeString, Optional: true, @@ -126,6 +142,11 @@ func resourceIdp() *schema.Resource { Required: true, Sensitive: true, }, + "client_secret_encrypted": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "authentication_url": { Type: schema.TypeString, Optional: true, @@ -192,7 +213,19 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - err := client.DeleteIDP(d.Id()) + idpID := d.Id() + cfClientIDP, err := client.GetIdpByID(idpID) + + if err != nil { + log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) + return err + } + + if len(cfClientIDP.Accounts) < 1 { + return errors.New("It is not allowed to delete IDPs without any assigned accounts as they are considered global") + } + + err = client.DeleteIDP(d.Id()) if err != nil { log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) @@ -225,34 +258,37 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("client_type", cfClientIDP.ClientType) if cfClientIDP.ClientType == "github" { - d.Set("github.0.client_id", cfClientIDP.ClientId) - d.Set("github.0.client_secret", cfClientIDP.ClientSecret) - d.Set("github.0.authentication_url", cfClientIDP.AuthURL) - d.Set("github.0.token_url", cfClientIDP.TokenURL) - d.Set("github.0.user_profile_url", cfClientIDP.UserProfileURL) - d.Set("github.0.api_host", cfClientIDP.ApiHost) - d.Set("github.0.api_path_prefix", cfClientIDP.ApiPathPrefix) - - // mapSlice := []map[string]interface{}{} - // map1 := map[string]interface{}{ - // "client_id": cfClientIDP.ClientId, - // "client_secret": cfClientIDP.ClientSecret, - // "authentication_url": cfClientIDP.AuthURL, - // "token_url": cfClientIDP.TokenURL, - // "user_profile_url": cfClientIDP.UserProfileURL, - // "api_host": cfClientIDP.ApiHost, - // "api_path_prefix": cfClientIDP.ApiPathPrefix, - // } - // mapSlice = append(mapSlice, map1) - // d.Set("github",mapSlice) + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + // Codefresh API Returns the client secret as an encrypted string on the server side + // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret + // for Terraform to properly calculate the diff + "client_secret": d.Get("github.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "token_url": cfClientIDP.TokenURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_host": cfClientIDP.ApiHost, + "api_path_prefix": cfClientIDP.ApiPathPrefix, + }} + + d.Set("github", attributes) } if cfClientIDP.ClientType == "gitlab" { - d.Set("gitlab.0.client_id", cfClientIDP.ClientId) - d.Set("gitlab.0.client_secret", cfClientIDP.ClientSecret) - d.Set("gitlab.0.authentication_url", cfClientIDP.AuthURL) - d.Set("gitlab.0.user_profile_url", cfClientIDP.UserProfileURL) - d.Set("gitlab.0.api_url", cfClientIDP.ApiURL) + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + // Codefresh API Returns the client secret as an encrypted string on the server side + // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret + // for Terraform to properly calculate the diff + "client_secret": d.Get("gitlab.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_url": cfClientIDP.ApiURL, + }} + + d.Set("gitlab", attributes) } return nil @@ -268,16 +304,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { LoginUrl: d.Get("login_url").(string), } - if d.Get("client_type") != nil { - cfClientIDP.ClientType = d.Get("client_type").(string) - } else { - cfClientIDP.ClientType = "" - } - if _, ok := d.GetOk("github"); ok { - if cfClientIDP.ClientType == "" { - cfClientIDP.ClientType = "github" - } + cfClientIDP.ClientType = "github" cfClientIDP.ClientId = d.Get("github.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) @@ -288,9 +316,6 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { } if _, ok := d.GetOk("gitlab"); ok { - if cfClientIDP.ClientType == "" { - cfClientIDP.ClientType = "gitlab" - } cfClientIDP.ClientType = "gitlab" cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) @@ -300,6 +325,9 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { } // if idpAttributes, ok := d.GetOk("github"); ok { + // if cfClientIDP.ClientType == "" { + // cfClientIDP.ClientType = "github" + // } // ghAttributes := idpAttributes.(*schema.Set).List()[0].(map[string]interface{}) // cfClientIDP.ClientType = "github" // cfClientIDP.ClientId = ghAttributes["client_id"].(string) From f2bfd3c5ef840692fb76005ba462b53465ec9294 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 6 Feb 2024 20:05:22 +0200 Subject: [PATCH 07/25] fix idp update unmarshall error --- codefresh/cfclient/idp.go | 22 ++++++++++++---------- codefresh/resource_idp.go | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 84f3b097..8683058d 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -77,12 +77,14 @@ func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { return &respIDP, nil } -func (client *Client) UpdateIDP(idp *IDP) (*IDP, error) { +// Currently on update the API returns a different structure for accounts than on read making the client crash on decode +// For now we are disabling response decode and in the resource will instead call the read function again +func (client *Client) UpdateIDP(idp *IDP) error { body, err := EncodeToJSON(idp) if err != nil { - return nil, err + return err } opts := RequestOptions{ Path: "/admin/idp", @@ -90,20 +92,20 @@ func (client *Client) UpdateIDP(idp *IDP) (*IDP, error) { Body: body, } - resp, err := client.RequestAPI(&opts) + _, err = client.RequestAPI(&opts) if err != nil { log.Printf("[DEBUG] Call to API for IDP update failed with Error = %v for Body %v", err, body) - return nil, err + return err } - var respIDP IDP - err = DecodeResponseInto(resp, &respIDP) - if err != nil { - return nil, err - } + // var respIDP IDP + // err = DecodeResponseInto(resp, &respIDP) + // if err != nil { + // return nil, err + // } - return &respIDP, nil + return nil } func (client *Client) DeleteIDP(id string) error { diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 1847c1dc..5a09dd49 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -238,7 +238,7 @@ func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - _, err := client.UpdateIDP(mapResourceToIDP(d)) + err := client.UpdateIDP(mapResourceToIDP(d)) if err != nil { log.Printf("[DEBUG] Error while updating idp. Error = %v", err) From 4ba6c4618d63d71d8d09b62ba82e03be09ca46d8 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 6 Feb 2024 20:06:16 +0200 Subject: [PATCH 08/25] cleanup --- codefresh/resource_idp.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 5a09dd49..4181708f 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -324,20 +324,5 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) } - // if idpAttributes, ok := d.GetOk("github"); ok { - // if cfClientIDP.ClientType == "" { - // cfClientIDP.ClientType = "github" - // } - // ghAttributes := idpAttributes.(*schema.Set).List()[0].(map[string]interface{}) - // cfClientIDP.ClientType = "github" - // cfClientIDP.ClientId = ghAttributes["client_id"].(string) - // cfClientIDP.ClientSecret = ghAttributes["client_secret"].(string) - // cfClientIDP.AuthURL = ghAttributes["authentication_url"].(string) - // cfClientIDP.TokenURL = ghAttributes["token_url"].(string) - // cfClientIDP.UserProfileURL = ghAttributes["user_profile_url"].(string) - // cfClientIDP.ApiHost = ghAttributes["api_host"].(string) - // cfClientIDP.ApiPathPrefix = ghAttributes["api_path_prefix"].(string) - // } - return cfClientIDP } From 7cd7e3219af01710ba0d5daec0efd80fdcb2c6aa Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 7 Feb 2024 20:59:10 +0200 Subject: [PATCH 09/25] add account scoped idp --- codefresh/cfclient/idp.go | 62 +++++++++++++- codefresh/resource_idp.go | 176 +++++++++++++++++++++++++++++++++----- 2 files changed, 214 insertions(+), 24 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 8683058d..705c65a4 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -9,6 +9,7 @@ import ( type IDP struct { ID string `json:"_id,omitempty"` + IsGlobal bool `json:"isGlobal,omitempty"` // This is not part of the schema, rather it is used to determine if the IDP is gloal or not and choose endpoints accordingly Access_token string `json:"access_token,omitempty"` Accounts []string `json:"accounts,omitempty"` ClientName string `json:"clientName,omitempty"` // IDP name @@ -46,6 +47,18 @@ type IDP struct { TokenURL string `json:"tokenURL,omitempty"` // Github, Gitlab UserProfileURL string `json:"userProfileURL,omitempty"` + // Okta + SyncMirrorAccounts []string `json:"syncMirrorAccounts,omitempty"` +} + +// Return the appropriate API endpoint for platform and account scoped IDPs +func getAPIEndpoint(isGlobal bool) string { + // If IDP is platform scoped + if isGlobal { + return "/admin/idp" + } else { + return "/idp/account" + } } func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { @@ -56,7 +69,7 @@ func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { return nil, err } opts := RequestOptions{ - Path: "/admin/idp", + Path: getAPIEndpoint(idp.IsGlobal), Method: "POST", Body: body, } @@ -87,7 +100,7 @@ func (client *Client) UpdateIDP(idp *IDP) error { return err } opts := RequestOptions{ - Path: "/admin/idp", + Path: getAPIEndpoint(idp.IsGlobal), Method: "PUT", Body: body, } @@ -109,8 +122,8 @@ func (client *Client) UpdateIDP(idp *IDP) error { } func (client *Client) DeleteIDP(id string) error { - - fullPath := fmt.Sprintf("/admin/idp/%s", url.PathEscape(id)) + baseUrl := getAPIEndpoint(true) + fullPath := fmt.Sprintf("%s/%s", baseUrl, url.PathEscape(id)) opts := RequestOptions{ Path: fullPath, Method: "DELETE", @@ -125,6 +138,31 @@ func (client *Client) DeleteIDP(id string) error { return nil } +func (client *Client) DeleteIDPAccount(id string) error { + + body, err := EncodeToJSON(map[string]interface{}{"id": id}) + + if err != nil { + return err + } + + opts := RequestOptions{ + Path: getAPIEndpoint(false), + Method: "DELETE", + Body: body, + } + + _, err = client.RequestAPI(&opts) + + if err != nil { + return err + } + + return nil +} + + + // get all idps func (client *Client) GetIDPs() (*[]IDP, error) { fullPath := "/admin/idp" @@ -206,6 +244,22 @@ func (client *Client) GetAccountIDPs() (*[]IDP, error) { return &idps, nil } +func (client *Client) GetAccountIdpByID(idpID string) (*IDP, error) { + + idpList, err := client.GetAccountIDPs() + if err != nil { + return nil, err + } + + for _, idp := range *idpList { + if idp.ID == idpID { + return &idp, nil + } + } + + return nil, errors.New(fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", idpID)) +} + // add account to idp func (client *Client) AddAccountToIDP(accountId, idpId string) error { diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 4181708f..eefae9dc 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -5,12 +5,16 @@ import ( "log" "errors" "context" + "regexp" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) +var supportedIdps = []string{"github","gitlab", "okta"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -38,8 +42,27 @@ func resourceIdp() *schema.Resource { return false } }), + // If name has changed for an account scoped IDP the provider needs to ignore it as the API always generates the name + customdiff.If(func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { + bIsGlobal := d.Get("is_global").(bool) + return !bIsGlobal + }, + func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error{ + old, _ := d.GetChange("name") + if err := d.SetNew("name",old); err != nil { + return err + } + return nil + }), ), Schema: map[string]*schema.Schema{ + "is_global": { + Type: schema.TypeBool, + Description: "If set to true IDP will be created globally for the entire platform - this requires a platform admin token. If false the IDP will be created at the level of a single account which is derived from the API token used. Defaults to false", + Optional: true, + Default: false, + ForceNew: true, + }, "display_name": { Description: "The display name for the IDP.", Type: schema.TypeString, @@ -80,45 +103,53 @@ func resourceIdp() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - ExactlyOneOf: []string{"gitlab"}, + ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { Type: schema.TypeString, + Description: "Client ID from Github", Required: true, }, "client_secret": { Type: schema.TypeString, + Description: "Client secret from GitHub", Required: true, Sensitive: true, }, "client_secret_encrypted": { Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", Optional: true, Computed: true, }, "authentication_url": { Type: schema.TypeString, + Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", Optional: true, Default: "https://github.com/login/oauth/authorize", }, "token_url": { Type: schema.TypeString, + Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", Optional: true, Default: "https://github.com/login/oauth/access_token", }, "user_profile_url": { Type: schema.TypeString, + Description: "GitHub user profile url, Defaults to https://api.github.com/user", Optional: true, Default: "https://api.github.com/user", }, "api_host": { Type: schema.TypeString, + Description: "GitHub API host, Defaults to api.github.com", Optional: true, Default: "api.github.com", }, "api_path_prefix": { Type: schema.TypeString, + Description: "GitHub API url path prefix, defaults to /", Optional: true, Default: "/", }, @@ -130,37 +161,96 @@ func resourceIdp() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - ExactlyOneOf: []string{"github"}, + ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { Type: schema.TypeString, + Description: "Client ID from Gitlab", Required: true, }, "client_secret": { Type: schema.TypeString, + Description: "Client secret from Gitlab", Required: true, Sensitive: true, }, "client_secret_encrypted": { Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", Optional: true, Computed: true, }, "authentication_url": { Type: schema.TypeString, + Description: "Authentication url, Defaults to https://gitlab.com", Optional: true, Default: "https://gitlab.com", }, "user_profile_url": { Type: schema.TypeString, + Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", Optional: true, - Default: "https://api.github.com/user", + Default: "https://gitlab.com/api/v4/user", }, "api_url": { Type: schema.TypeString, + Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", + Optional: true, + Default: "https://gitlab.com/api/v4/", + }, + }, + }, + }, + "okta": { + Description: "Settings for Okta IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Okta", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "The OKTA organization URL, for example, https://.okta.com", + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`^(https?:\\/\\/)(\\S+)(\\.okta(preview|-emea)?\\.com$)`), "must be a valid okta url"), + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your OKTA organization", Optional: true, - Default: "api.github.com", + }, + "app_id_encrypted": { + Type: schema.TypeString, + Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "sync_mirror_accounts": { + Type: schema.TypeList, + Description: "The names of the additional Codefresh accounts to be synced from Okta", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, }, }, @@ -188,8 +278,17 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) idpID := d.Id() + isGlobal := d.Get("is_global").(bool) + + var cfClientIDP *cfclient.IDP + var err error - cfClientIDP, err := client.GetIdpByID(idpID) + if isGlobal { + cfClientIDP, err = client.GetIdpByID(idpID) + } else + { + cfClientIDP, err = client.GetAccountIdpByID(idpID) + } if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { @@ -214,23 +313,39 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) idpID := d.Id() - cfClientIDP, err := client.GetIdpByID(idpID) + isGlobal := d.Get("is_global").(bool) - if err != nil { - log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) - return err - } + var cfClientIDP *cfclient.IDP + var err error - if len(cfClientIDP.Accounts) < 1 { - return errors.New("It is not allowed to delete IDPs without any assigned accounts as they are considered global") - } + if isGlobal { + cfClientIDP, err = client.GetIdpByID(idpID) - err = client.DeleteIDP(d.Id()) + if err != nil { + log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) + return err + } - if err != nil { - log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) - return err + if len(cfClientIDP.Accounts) < 1 { + return errors.New("It is not allowed to delete IDPs without any assigned accounts as they are considered global. Assign at least one account before deleting") + } + + err = client.DeleteIDP(d.Id()) + + if err != nil { + log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) + return err + } + } else + { + err = client.DeleteIDPAccount(d.Id()) + + if err != nil { + log.Printf("[DEBUG] Error while deleting account level IDP. Error = %v", err) + return err + } } + return nil } @@ -278,9 +393,6 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "gitlab" { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, - // Codefresh API Returns the client secret as an encrypted string on the server side - // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret - // for Terraform to properly calculate the diff "client_secret": d.Get("gitlab.0.client_secret"), "client_secret_encrypted": cfClientIDP.ClientSecret, "authentication_url": cfClientIDP.AuthURL, @@ -291,12 +403,27 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("gitlab", attributes) } + if cfClientIDP.ClientType == "okta" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("okta.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "client_host": cfClientIDP.ClientHost, + "app_id": d.Get("okta.0.app_id"), + "app_id_encrypted": cfClientIDP.AppId, + "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + }} + + d.Set("okta", attributes) + } + return nil } func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP := &cfclient.IDP{ ID: d.Id(), + IsGlobal: d.Get("is_global").(bool), DisplayName: d.Get("display_name").(string), ClientName: d.Get("name").(string), RedirectUrl: d.Get("redirect_url").(string), @@ -324,5 +451,14 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) } + if _, ok := d.GetOk("okta"); ok { + cfClientIDP.ClientType = "okta" + cfClientIDP.ClientId = d.Get("okta.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("okta.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("okta.0.client_host").(string) + cfClientIDP.AppId = d.Get("okta.0.app_id").(string) + cfClientIDP.SyncMirrorAccounts = datautil.ConvertStringArr(d.Get("okta.0.sync_mirror_accounts").([]interface{})) + } + return cfClientIDP } From 9d30f94d2911044e8c26d40bc6c543dc3cfff6e0 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 7 Feb 2024 21:03:43 +0200 Subject: [PATCH 10/25] add account scoped idp --- codefresh/resource_idp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index eefae9dc..0c354c9c 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -58,7 +58,7 @@ func resourceIdp() *schema.Resource { Schema: map[string]*schema.Schema{ "is_global": { Type: schema.TypeBool, - Description: "If set to true IDP will be created globally for the entire platform - this requires a platform admin token. If false the IDP will be created at the level of a single account which is derived from the API token used. Defaults to false", + Description: "If set to true IDP will be created globally for the entire platform - this requires a platform admin token and is meant for on-prem installations of Codefresh. If false the IDP will be created at the level of a single account which is derived from the API token used. Defaults to false", Optional: true, Default: false, ForceNew: true, From 0e9c04204650e68bc936ce83e9e79146cd19d796 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 8 Feb 2024 12:52:30 +0200 Subject: [PATCH 11/25] add google and auth0 --- codefresh/cfclient/idp.go | 8 +++ codefresh/resource_idp.go | 132 +++++++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 705c65a4..dd1af2ac 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -49,6 +49,14 @@ type IDP struct { UserProfileURL string `json:"userProfileURL,omitempty"` // Okta SyncMirrorAccounts []string `json:"syncMirrorAccounts,omitempty"` + // Google, Ldap + AllowedGroupsForSync string `json:"allowedGroupsForSync,omitempty"` + // Google + Subject string `json:"subject,omitempty"` + // Google + KeyFile string `json:"keyfile,omitempty"` + // Google + SyncField string `json:"syncField,omitempty"` } // Return the appropriate API endpoint for platform and account scoped IDPs diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 0c354c9c..75eef9f1 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -14,7 +14,7 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) -var supportedIdps = []string{"github","gitlab", "okta"} +var supportedIdps = []string{"github","gitlab", "okta", "google","auth0"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -255,6 +255,93 @@ func resourceIdp() *schema.Resource { }, }, }, + "google": { + Description: "Settings for Google IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Google, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Google", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "json_keyfile_encrypted": { + Type: schema.TypeString, + Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Comma separated list of groups to sync", + Optional: true, + }, + "sync_filed": { + Type: schema.TypeString, + Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", + Optional: true, + }, + }, + }, + }, + "auth0": { + Description: "Settings for Auth0 IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Auth0", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Auth0", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain of the Auth0 application", + Required: true, + }, + }, + }, + }, }, } } @@ -417,6 +504,32 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("okta", attributes) } + if cfClientIDP.ClientType == "google" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("google.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "admin_email": cfClientIDP.Subject, + "json_keyfile": d.Get("google.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "sync_filed": cfClientIDP.SyncField, + }} + + d.Set("google", attributes) + } + + if cfClientIDP.ClientType == "auth0" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("auth0.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, + }} + + d.Set("auth0", attributes) + } + return nil } @@ -460,5 +573,22 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.SyncMirrorAccounts = datautil.ConvertStringArr(d.Get("okta.0.sync_mirror_accounts").([]interface{})) } + if _, ok := d.GetOk("google"); ok { + cfClientIDP.ClientType = "google" + cfClientIDP.ClientId = d.Get("google.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("google.0.client_secret").(string) + cfClientIDP.KeyFile = d.Get("google.0.json_keyfile").(string) + cfClientIDP.Subject = d.Get("google.0.admin_email").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("google.0.allowed_groups_for_sync").(string) + cfClientIDP.SyncField = d.Get("google.0.sync_filed").(string) + } + + if _, ok := d.GetOk("auth0"); ok { + cfClientIDP.ClientType = "auth0" + cfClientIDP.ClientId = d.Get("auth0.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("auth0.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("auth0.0.domain").(string) + } + return cfClientIDP } From 3cd8e5cfa8c4f93ccd046138dc23956cbb21901f Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sat, 10 Feb 2024 10:23:52 +0200 Subject: [PATCH 12/25] add azure --- codefresh/cfclient/idp.go | 4 +++ codefresh/resource_idp.go | 75 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index dd1af2ac..67cb3b23 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -57,6 +57,10 @@ type IDP struct { KeyFile string `json:"keyfile,omitempty"` // Google SyncField string `json:"syncField,omitempty"` + // Azure + AutoGroupSync bool `json:"autoGroupSync,omitempty"` + // Azure + SyncInterval int `json:"syncInterval,string,omitempty"` } // Return the appropriate API endpoint for platform and account scoped IDPs diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 75eef9f1..fb1e5850 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -14,7 +14,7 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) -var supportedIdps = []string{"github","gitlab", "okta", "google","auth0"} +var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -342,6 +342,55 @@ func resourceIdp() *schema.Resource { }, }, }, + "azure": { + Description: "Settings for Azure IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Azure", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Application ID from your Enterprise Application Properties in Azure AD", + Required: true, + }, + "tenant": { + Type: schema.TypeString, + Description: "Azure tenant", + Optional: true, + }, + "object_id": { + Type: schema.TypeString, + Description: "The Object ID from your Enterprise Application Properties in Azure AD", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + }, + }, + }, }, } } @@ -530,6 +579,20 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("auth0", attributes) } + if cfClientIDP.ClientType == "azure" { + attributes := []map[string]interface{}{{ + "app_id": cfClientIDP.ClientId, + "client_secret": d.Get("azure.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "object_id": cfClientIDP.AppId, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "sync_interval": cfClientIDP.SyncInterval, + "tenant": cfClientIDP.Tenant, + }} + + d.Set("azure", attributes) + } + return nil } @@ -590,5 +653,15 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ClientHost = d.Get("auth0.0.domain").(string) } + if _, ok := d.GetOk("azure"); ok { + cfClientIDP.ClientType = "azure" + cfClientIDP.ClientId = d.Get("azure.0.app_id").(string) + cfClientIDP.ClientSecret = d.Get("azure.0.client_secret").(string) + cfClientIDP.AppId = d.Get("azure.0.object_id").(string) + cfClientIDP.Tenant = d.Get("azure.0.tenant").(string) + cfClientIDP.AutoGroupSync = d.Get("azure.0.autosync_teams_and_users").(bool) + cfClientIDP.SyncInterval = d.Get("azure.0.sync_interval").(int) + } + return cfClientIDP } From af8289fcdbac08436a9c3aeac7183734011d6228 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 11 Feb 2024 15:31:31 +0200 Subject: [PATCH 13/25] Add onelogin and keycloak --- codefresh/cfclient/idp.go | 19 +- .../internal/schemautil/validation_strings.go | 2 +- codefresh/resource_idp.go | 175 +++++++++++++++++- 3 files changed, 182 insertions(+), 14 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 67cb3b23..b67e6935 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -9,7 +9,6 @@ import ( type IDP struct { ID string `json:"_id,omitempty"` - IsGlobal bool `json:"isGlobal,omitempty"` // This is not part of the schema, rather it is used to determine if the IDP is gloal or not and choose endpoints accordingly Access_token string `json:"access_token,omitempty"` Accounts []string `json:"accounts,omitempty"` ClientName string `json:"clientName,omitempty"` // IDP name @@ -60,7 +59,15 @@ type IDP struct { // Azure AutoGroupSync bool `json:"autoGroupSync,omitempty"` // Azure - SyncInterval int `json:"syncInterval,string,omitempty"` + SyncInterval string `json:"syncInterval,omitempty"` + // Onelogin + ApiClientId string `json:"apiClientId,omitempty"` + // Onelogin + ApiClientSecret string `json:"apiClientSecret,omitempty"` + // Keycloak + Host string `json:"host,omitempty"` + // keycloak + Realm string `json:"realm,omitempty"` } // Return the appropriate API endpoint for platform and account scoped IDPs @@ -73,7 +80,7 @@ func getAPIEndpoint(isGlobal bool) string { } } -func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { +func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (*IDP, error) { body, err := EncodeToJSON(idp) @@ -81,7 +88,7 @@ func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { return nil, err } opts := RequestOptions{ - Path: getAPIEndpoint(idp.IsGlobal), + Path: getAPIEndpoint(isGlobal), Method: "POST", Body: body, } @@ -104,7 +111,7 @@ func (client *Client) CreateIDP(idp *IDP) (*IDP, error) { // Currently on update the API returns a different structure for accounts than on read making the client crash on decode // For now we are disabling response decode and in the resource will instead call the read function again -func (client *Client) UpdateIDP(idp *IDP) error { +func (client *Client) UpdateIDP(idp *IDP, isGlobal bool) error { body, err := EncodeToJSON(idp) @@ -112,7 +119,7 @@ func (client *Client) UpdateIDP(idp *IDP) error { return err } opts := RequestOptions{ - Path: getAPIEndpoint(idp.IsGlobal), + Path: getAPIEndpoint(isGlobal), Method: "PUT", Body: body, } diff --git a/codefresh/internal/schemautil/validation_strings.go b/codefresh/internal/schemautil/validation_strings.go index 0f47b009..78af3b03 100644 --- a/codefresh/internal/schemautil/validation_strings.go +++ b/codefresh/internal/schemautil/validation_strings.go @@ -96,4 +96,4 @@ func StringMatchesRegExp(regex string, opts ...ValidationOptionSetter) schema.Sc } return diags } -} +} \ No newline at end of file diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index fb1e5850..ca9f0a7e 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -6,6 +6,7 @@ import ( "errors" "context" "regexp" + "strconv" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -14,7 +15,7 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) -var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure"} +var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure","onelogin","keycloak"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -230,7 +231,7 @@ func resourceIdp() *schema.Resource { "client_host": { Type: schema.TypeString, Description: "The OKTA organization URL, for example, https://.okta.com", - ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`^(https?:\\/\\/)(\\S+)(\\.okta(preview|-emea)?\\.com$)`), "must be a valid okta url"), + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), Required: true, }, "app_id": { @@ -391,6 +392,96 @@ func resourceIdp() *schema.Resource { }, }, }, + "onelogin": { + Description: "Settings for onelogin IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Onelogin", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Onelogin", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain to be used for authentication", + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your Onelogin", + Optional: true, + }, + "api_client_id": { + Type: schema.TypeString, + Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + }, + "api_client_secret": { + Type: schema.TypeString, + Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated + //DiffSuppressFunc: surpressObfuscatedFields(), + }, + }, + }, + }, + "keycloak": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Keycloak", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Keycloak", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "host": { + Type: schema.TypeString, + Description: "The Keycloak URL", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), + }, + "realm": { + Type: schema.TypeString, + Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", + Optional: true, + Default: "master", + }, + }, + }, + }, }, } } @@ -399,7 +490,7 @@ func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - resp, err := client.CreateIDP(mapResourceToIDP(d)) + resp, err := client.CreateIDP(mapResourceToIDP(d),d.Get("is_global").(bool)) if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) @@ -489,7 +580,7 @@ func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - err := client.UpdateIDP(mapResourceToIDP(d)) + err := client.UpdateIDP(mapResourceToIDP(d), d.Get("is_global").(bool)) if err != nil { log.Printf("[DEBUG] Error while updating idp. Error = %v", err) @@ -500,6 +591,7 @@ func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { } func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { + isGlobal := d.Get("is_global").(bool) d.SetId(cfClientIDP.ID) d.Set("display_name", cfClientIDP.DisplayName) d.Set("name", cfClientIDP.ClientName) @@ -580,26 +672,66 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { } if cfClientIDP.ClientType == "azure" { + + syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) + + if err != nil { + return err + } + attributes := []map[string]interface{}{{ "app_id": cfClientIDP.ClientId, "client_secret": d.Get("azure.0.client_secret"), "client_secret_encrypted": cfClientIDP.ClientSecret, "object_id": cfClientIDP.AppId, "autosync_teams_and_users": cfClientIDP.AutoGroupSync, - "sync_interval": cfClientIDP.SyncInterval, + "sync_interval": syncInterval, "tenant": cfClientIDP.Tenant, }} d.Set("azure", attributes) } + if cfClientIDP.ClientType == "onelogin" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("onelogin.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, + "api_client_id": cfClientIDP.ApiClientId, + + "api_client_secret": cfClientIDP.ApiClientSecret, + "app_id": cfClientIDP.AppId, + }} + // When account scoped, Client secret is returned obfuscated after first apply, causing diff to appear everytime. + // This behavior would always set the API clint secret from the resource, allowing at least changing the secret when the value in terraform configuration changes. + // Though it would not detect drift if the secret is changed from UI. + if !isGlobal{ + attributes[0]["api_client_secret"] = d.Get("onelogin.0.api_client_secret") + } + + d.Set("onelogin", attributes) + } + + if cfClientIDP.ClientType == "keycloak" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("keycloak.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "host": cfClientIDP.Host, + "realm": cfClientIDP.Realm, + }} + + d.Set("keycloak", attributes) + } + return nil } func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { + cfClientIDP := &cfclient.IDP{ ID: d.Id(), - IsGlobal: d.Get("is_global").(bool), DisplayName: d.Get("display_name").(string), ClientName: d.Get("name").(string), RedirectUrl: d.Get("redirect_url").(string), @@ -660,8 +792,37 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.AppId = d.Get("azure.0.object_id").(string) cfClientIDP.Tenant = d.Get("azure.0.tenant").(string) cfClientIDP.AutoGroupSync = d.Get("azure.0.autosync_teams_and_users").(bool) - cfClientIDP.SyncInterval = d.Get("azure.0.sync_interval").(int) + cfClientIDP.SyncInterval = strconv.Itoa(d.Get("azure.0.sync_interval").(int)) + } + + if _, ok := d.GetOk("onelogin"); ok { + cfClientIDP.ClientType = "onelogin" + cfClientIDP.ClientId = d.Get("onelogin.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("onelogin.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("onelogin.0.domain").(string) + cfClientIDP.AppId = d.Get("onelogin.0.app_id").(string) + cfClientIDP.ApiClientId = d.Get("onelogin.0.api_client_id").(string) + cfClientIDP.ApiClientSecret = d.Get("onelogin.0.api_client_secret").(string) + } + + if _, ok := d.GetOk("keycloak"); ok { + cfClientIDP.ClientType = "keycloak" + cfClientIDP.ClientId = d.Get("keycloak.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("keycloak.0.client_secret").(string) + cfClientIDP.Host = d.Get("keycloak.0.host").(string) + cfClientIDP.Realm = d.Get("keycloak.0.realm").(string) } return cfClientIDP } + +// func surpressObfuscatedFields() schema.SchemaDiffSuppressFunc { +// return func(k, old, new string, d *schema.ResourceData) bool { +// if old == "*****" { +// return true +// } else { +// return false +// } +// } +// } + From 50741e97959d50278725978ee5794c56d2746fd8 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 11 Feb 2024 19:58:58 +0200 Subject: [PATCH 14/25] add saml --- codefresh/cfclient/idp.go | 26 ++++-- codefresh/resource_idp.go | 187 ++++++++++++++++++++++++++++++++++---- 2 files changed, 187 insertions(+), 26 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index b67e6935..7c8daf58 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -58,6 +58,8 @@ type IDP struct { SyncField string `json:"syncField,omitempty"` // Azure AutoGroupSync bool `json:"autoGroupSync,omitempty"` + // Google,Okta,saml + ActivateUserAfterSync bool `json:"activateUserAfterSync,omitempty"` // Azure SyncInterval string `json:"syncInterval,omitempty"` // Onelogin @@ -68,6 +70,12 @@ type IDP struct { Host string `json:"host,omitempty"` // keycloak Realm string `json:"realm,omitempty"` + // SAML + EntryPoint string `json:"entryPoint,omitempty"` + // SAML + ApplicationCert string `json:"cert,omitempty"` + // SAML + SamlProvider string `json:"provider,omitempty"` } // Return the appropriate API endpoint for platform and account scoped IDPs @@ -80,12 +88,17 @@ func getAPIEndpoint(isGlobal bool) string { } } -func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (*IDP, error) { +// Currently on create the API sometimes (like when creating saml idps) returns a different structure for accounts than on read making the client crash on decode +// For now we are disabling response decode and in the resource will instead call the read function again +func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (id string, err error) { body, err := EncodeToJSON(idp) + strBody := string(body) + fmt.Println(strBody) + if err != nil { - return nil, err + return "",err } opts := RequestOptions{ Path: getAPIEndpoint(isGlobal), @@ -97,16 +110,17 @@ func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (*IDP, error) { if err != nil { log.Printf("[DEBUG] Call to API for IDP creation failed with Error = %v for Body %v", err, body) - return nil, err + return "",err } - var respIDP IDP + var respIDP map[string]interface{} err = DecodeResponseInto(resp, &respIDP) + if err != nil { - return nil, err + return "",nil } - return &respIDP, nil + return respIDP["id"].(string),nil } // Currently on update the API returns a different structure for accounts than on read making the client crash on decode diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index ca9f0a7e..66cdf668 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -15,7 +15,7 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) -var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure","onelogin","keycloak"} +var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure","onelogin","keycloak","saml"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -253,6 +253,17 @@ func resourceIdp() *schema.Resource { Type: schema.TypeString, }, }, + "access_token": { + Type: schema.TypeString, + Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + "access_token_encrypted": { + Type: schema.TypeString, + Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, }, }, }, @@ -286,6 +297,12 @@ func resourceIdp() *schema.Resource { Description: "Email of a user with admin permissions on google, relevant only for synchronization", Optional: true, }, + "admin_email_encrypted": { + Type: schema.TypeString, + Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, "json_keyfile": { Type: schema.TypeString, Description: "JSON keyfile for google service account used for synchronization", @@ -293,7 +310,7 @@ func resourceIdp() *schema.Resource { }, "json_keyfile_encrypted": { Type: schema.TypeString, - Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", Optional: true, Computed: true, }, @@ -302,7 +319,7 @@ func resourceIdp() *schema.Resource { Description: "Comma separated list of groups to sync", Optional: true, }, - "sync_filed": { + "sync_field": { Type: schema.TypeString, Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", Optional: true, @@ -482,6 +499,95 @@ func resourceIdp() *schema.Resource { }, }, }, + "saml": { + Description: "Settings for SAML IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint": { + Type: schema.TypeString, + Description: "The SSO endpoint of your Identity Provider", + Required: true, + }, + "application_certificate": { + Type: schema.TypeString, + Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", + Required: true, + Sensitive: true, + }, + "application_certificate_encrypted": { + Type: schema.TypeString, + Description: "Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "provider": { + Type: schema.TypeString, + Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"","okta","GSuite"},false), + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Valid for GSuite only: Comma separated list of groups to sync", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + "activate_users_after_sync": { + Type: schema.TypeBool, + Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", + Optional: true, + Default: false, + }, + "app_id": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Codefresh application ID in Okta", + Optional: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", + Optional: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "json_keyfile_encrypted": { + Type: schema.TypeString, + Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "admin_email_encrypted": { + Type: schema.TypeString, + Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + }, + }, + }, }, } } @@ -490,14 +596,14 @@ func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - resp, err := client.CreateIDP(mapResourceToIDP(d),d.Get("is_global").(bool)) + id,err := client.CreateIDP(mapResourceToIDP(d),d.Get("is_global").(bool)) if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) return err } - d.SetId(resp.ID) + d.SetId(id) return resourceIDPRead(d, meta) } @@ -640,6 +746,8 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "app_id": d.Get("okta.0.app_id"), "app_id_encrypted": cfClientIDP.AppId, "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + "access_token": d.Get("okta.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, }} d.Set("okta", attributes) @@ -650,13 +758,21 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), "client_secret_encrypted": cfClientIDP.ClientSecret, - "admin_email": cfClientIDP.Subject, + "admin_email": d.Get("google.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, "json_keyfile": d.Get("google.0.json_keyfile"), "json_keyfile_encrypted": cfClientIDP.KeyFile, "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, - "sync_filed": cfClientIDP.SyncField, + "sync_field": cfClientIDP.SyncField, }} + // When account scoped, admin email is returned obfuscated after first apply, causing diff to appear everytime. + // This behavior would always set the admin email from the resource, allowing at least changing the secret when the value in terraform configuration changes. + // Though it would not detect drift if the secret is changed from UI. + if !isGlobal{ + attributes[0]["admin_email"] = d.Get("google.0.admin_email") + } + d.Set("google", attributes) } @@ -725,6 +841,32 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("keycloak", attributes) } + if cfClientIDP.ClientType == "saml" { + syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) + + if err != nil { + return err + } + attributes := []map[string]interface{}{{ + "endpoint": cfClientIDP.EntryPoint, + "application_certificate": d.Get("saml.0.application_certificate"), + "application_certificate_encrypted": cfClientIDP.ApplicationCert, + "provider": cfClientIDP.SamlProvider, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, + "sync_interval": syncInterval, + "app_id": cfClientIDP.AppId, + "client_host": cfClientIDP.ClientHost, + "json_keyfile": d.Get("saml.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "admin_email": d.Get("saml.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, + }} + + d.Set("saml", attributes) + } + return nil } @@ -766,6 +908,7 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ClientHost = d.Get("okta.0.client_host").(string) cfClientIDP.AppId = d.Get("okta.0.app_id").(string) cfClientIDP.SyncMirrorAccounts = datautil.ConvertStringArr(d.Get("okta.0.sync_mirror_accounts").([]interface{})) + cfClientIDP.Access_token = d.Get("okta.0.access_token").(string) } if _, ok := d.GetOk("google"); ok { @@ -775,7 +918,7 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.KeyFile = d.Get("google.0.json_keyfile").(string) cfClientIDP.Subject = d.Get("google.0.admin_email").(string) cfClientIDP.AllowedGroupsForSync = d.Get("google.0.allowed_groups_for_sync").(string) - cfClientIDP.SyncField = d.Get("google.0.sync_filed").(string) + cfClientIDP.SyncField = d.Get("google.0.sync_field").(string) } if _, ok := d.GetOk("auth0"); ok { @@ -813,16 +956,20 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Realm = d.Get("keycloak.0.realm").(string) } - return cfClientIDP -} - -// func surpressObfuscatedFields() schema.SchemaDiffSuppressFunc { -// return func(k, old, new string, d *schema.ResourceData) bool { -// if old == "*****" { -// return true -// } else { -// return false -// } -// } -// } + if _, ok := d.GetOk("saml"); ok { + cfClientIDP.ClientType = "saml" + cfClientIDP.SamlProvider = d.Get("saml.0.provider").(string) + cfClientIDP.EntryPoint = d.Get("saml.0.endpoint").(string) + cfClientIDP.ApplicationCert = d.Get("saml.0.application_certificate").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("saml.0.allowed_groups_for_sync").(string) + cfClientIDP.AutoGroupSync = d.Get("saml.0.autosync_teams_and_users").(bool) + cfClientIDP.ActivateUserAfterSync = d.Get("saml.0.activate_users_after_sync").(bool) + cfClientIDP.SyncInterval = strconv.Itoa(d.Get("saml.0.sync_interval").(int)) + cfClientIDP.AppId = d.Get("saml.0.app_id").(string) + cfClientIDP.ClientHost = d.Get("saml.0.client_host").(string) + cfClientIDP.KeyFile = d.Get("saml.0.json_keyfile").(string) + cfClientIDP.Subject = d.Get("saml.0.admin_email").(string) + } + return cfClientIDP +} \ No newline at end of file From 70420d725991aee13740be035c677e41b422fe31 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Sun, 11 Feb 2024 21:50:22 +0200 Subject: [PATCH 15/25] finalize saml --- codefresh/resource_idp.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 66cdf668..291ff2f6 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -565,7 +565,7 @@ func resourceIdp() *schema.Resource { }, "json_keyfile": { Type: schema.TypeString, - Description: "JSON keyfile for google service account used for synchronization", + Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", Optional: true, }, "json_keyfile_encrypted": { @@ -576,7 +576,7 @@ func resourceIdp() *schema.Resource { }, "admin_email": { Type: schema.TypeString, - Description: "Email of a user with admin permissions on google, relevant only for synchronization", + Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", Optional: true, }, "admin_email_encrypted": { @@ -585,6 +585,17 @@ func resourceIdp() *schema.Resource { Optional: true, Computed: true, }, + "access_token": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + "access_token_encrypted": { + Type: schema.TypeString, + Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, }, }, }, @@ -862,6 +873,8 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "json_keyfile_encrypted": cfClientIDP.KeyFile, "admin_email": d.Get("saml.0.admin_email"), "admin_email_encrypted": cfClientIDP.Subject, + "access_token": d.Get("saml.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, }} d.Set("saml", attributes) @@ -969,6 +982,7 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ClientHost = d.Get("saml.0.client_host").(string) cfClientIDP.KeyFile = d.Get("saml.0.json_keyfile").(string) cfClientIDP.Subject = d.Get("saml.0.admin_email").(string) + cfClientIDP.Access_token = d.Get("saml.0.access_token").(string) } return cfClientIDP From 33f1eaf5659a0b6163c218f44f4b38543da66866 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 12 Feb 2024 18:02:39 +0200 Subject: [PATCH 16/25] add ldap and format --- codefresh/cfclient/idp.go | 25 +- .../internal/schemautil/validation_strings.go | 2 +- codefresh/provider.go | 2 +- codefresh/resource_idp.go | 761 ++++++++++-------- templates/guides/development.md.tmpl | 2 +- 5 files changed, 444 insertions(+), 348 deletions(-) diff --git a/codefresh/cfclient/idp.go b/codefresh/cfclient/idp.go index 7c8daf58..aa213cd3 100644 --- a/codefresh/cfclient/idp.go +++ b/codefresh/cfclient/idp.go @@ -76,6 +76,14 @@ type IDP struct { ApplicationCert string `json:"cert,omitempty"` // SAML SamlProvider string `json:"provider,omitempty"` + // ldap + Password string `json:"password,omitempty"` + Url string `json:"url,omitempty"` + DistinguishedName string `json:"distinguishedName,omitempty"` + SearchBase string `json:"searchBase,omitempty"` + SearchFilter string `json:"searchFilter,omitempty"` + SearchBaseForSync string `json:"searchBaseForSync,omitempty"` + Certificate string `json:"certificate,omitempty"` } // Return the appropriate API endpoint for platform and account scoped IDPs @@ -94,11 +102,8 @@ func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (id string, err error) body, err := EncodeToJSON(idp) - strBody := string(body) - fmt.Println(strBody) - if err != nil { - return "",err + return "", err } opts := RequestOptions{ Path: getAPIEndpoint(isGlobal), @@ -110,17 +115,17 @@ func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (id string, err error) if err != nil { log.Printf("[DEBUG] Call to API for IDP creation failed with Error = %v for Body %v", err, body) - return "",err + return "", err } var respIDP map[string]interface{} err = DecodeResponseInto(resp, &respIDP) - + if err != nil { - return "",nil + return "", nil } - return respIDP["id"].(string),nil + return respIDP["id"].(string), nil } // Currently on update the API returns a different structure for accounts than on read making the client crash on decode @@ -182,7 +187,7 @@ func (client *Client) DeleteIDPAccount(id string) error { opts := RequestOptions{ Path: getAPIEndpoint(false), Method: "DELETE", - Body: body, + Body: body, } _, err = client.RequestAPI(&opts) @@ -194,8 +199,6 @@ func (client *Client) DeleteIDPAccount(id string) error { return nil } - - // get all idps func (client *Client) GetIDPs() (*[]IDP, error) { fullPath := "/admin/idp" diff --git a/codefresh/internal/schemautil/validation_strings.go b/codefresh/internal/schemautil/validation_strings.go index 78af3b03..0f47b009 100644 --- a/codefresh/internal/schemautil/validation_strings.go +++ b/codefresh/internal/schemautil/validation_strings.go @@ -96,4 +96,4 @@ func StringMatchesRegExp(regex string, opts ...ValidationOptionSetter) schema.Sc } return diags } -} \ No newline at end of file +} diff --git a/codefresh/provider.go b/codefresh/provider.go index 6d456a51..85eb230e 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -68,7 +68,7 @@ func Provider() *schema.Provider { "codefresh_user": resourceUser(), "codefresh_team": resourceTeam(), "codefresh_abac_rules": resourceGitopsAbacRule(), - "codefresh_idp": resourceIdp(), + "codefresh_idp": resourceIdp(), }, ConfigureFunc: configureProvider, } diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 291ff2f6..403c4be1 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -1,21 +1,21 @@ package codefresh import ( + "context" + "errors" "fmt" "log" - "errors" - "context" "regexp" "strconv" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" ) -var supportedIdps = []string{"github","gitlab", "okta", "google","auth0","azure","onelogin","keycloak","saml"} +var supportedIdps = []string{"github", "gitlab", "okta", "google", "auth0", "azure", "onelogin", "keycloak", "saml", "ldap"} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -47,22 +47,22 @@ func resourceIdp() *schema.Resource { customdiff.If(func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { bIsGlobal := d.Get("is_global").(bool) return !bIsGlobal - }, - func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error{ - old, _ := d.GetChange("name") - if err := d.SetNew("name",old); err != nil { - return err - } - return nil - }), + }, + func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + old, _ := d.GetChange("name") + if err := d.SetNew("name", old); err != nil { + return err + } + return nil + }), ), Schema: map[string]*schema.Schema{ "is_global": { - Type: schema.TypeBool, + Type: schema.TypeBool, Description: "If set to true IDP will be created globally for the entire platform - this requires a platform admin token and is meant for on-prem installations of Codefresh. If false the IDP will be created at the level of a single account which is derived from the API token used. Defaults to false", - Optional: true, - Default: false, - ForceNew: true, + Optional: true, + Default: false, + ForceNew: true, }, "display_name": { Description: "The display name for the IDP.", @@ -72,529 +72,595 @@ func resourceIdp() *schema.Resource { "name": { Description: "Name of the IDP, will be generated if not set", Type: schema.TypeString, - Computed: true, - Optional: true, + Computed: true, + Optional: true, }, "client_type": { Description: "Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc)", Type: schema.TypeString, - Computed: true, - ForceNew: true, + Computed: true, + ForceNew: true, }, "redirect_url": { Description: "API Callback url for the identity provider", Type: schema.TypeString, - Computed: true, + Computed: true, }, "redirect_ui_url": { Description: "UI Callback url for the identity provider", Type: schema.TypeString, - Computed: true, + Computed: true, }, "login_url": { - Type: schema.TypeString, + Type: schema.TypeString, Computed: true, }, "config_hash": { - Type: schema.TypeString, + Type: schema.TypeString, Computed: true, }, "github": { - Description: "Settings for GitHub IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for GitHub IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID from Github", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from GitHub", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "authentication_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", - Optional: true, - Default: "https://github.com/login/oauth/authorize", + Optional: true, + Default: "https://github.com/login/oauth/authorize", }, "token_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", - Optional: true, - Default: "https://github.com/login/oauth/access_token", + Optional: true, + Default: "https://github.com/login/oauth/access_token", }, "user_profile_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "GitHub user profile url, Defaults to https://api.github.com/user", - Optional: true, - Default: "https://api.github.com/user", + Optional: true, + Default: "https://api.github.com/user", }, "api_host": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "GitHub API host, Defaults to api.github.com", - Optional: true, - Default: "api.github.com", + Optional: true, + Default: "api.github.com", }, "api_path_prefix": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "GitHub API url path prefix, defaults to /", - Optional: true, - Default: "/", + Optional: true, + Default: "/", }, }, }, }, "gitlab": { - Description: "Settings for GitLab IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for GitLab IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID from Gitlab", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from Gitlab", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "authentication_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Authentication url, Defaults to https://gitlab.com", - Optional: true, - Default: "https://gitlab.com", + Optional: true, + Default: "https://gitlab.com", }, "user_profile_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", - Optional: true, - Default: "https://gitlab.com/api/v4/user", + Optional: true, + Default: "https://gitlab.com/api/v4/user", }, "api_url": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", - Optional: true, - Default: "https://gitlab.com/api/v4/", + Optional: true, + Default: "https://gitlab.com/api/v4/", }, }, }, }, "okta": { - Description: "Settings for Okta IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for Okta IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret in Okta", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "client_host": { - Type: schema.TypeString, - Description: "The OKTA organization URL, for example, https://.okta.com", + Type: schema.TypeString, + Description: "The OKTA organization URL, for example, https://.okta.com", ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), - Required: true, + Required: true, }, "app_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Codefresh application ID in your OKTA organization", - Optional: true, + Optional: true, }, "app_id_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "sync_mirror_accounts": { - Type: schema.TypeList, + Type: schema.TypeList, Description: "The names of the additional Codefresh accounts to be synced from Okta", - Optional: true, + Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, "access_token": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, + Optional: true, }, "access_token_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, }, }, }, "google": { - Description: "Settings for Google IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for Google IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID in Google, must be unique across all identity providers in Codefresh", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret in Google", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "admin_email": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, + Optional: true, }, "admin_email_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "json_keyfile": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "JSON keyfile for google service account used for synchronization", - Optional: true, + Optional: true, }, "json_keyfile_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "allowed_groups_for_sync": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Comma separated list of groups to sync", - Optional: true, + Optional: true, }, "sync_field": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", - Optional: true, + Optional: true, }, }, }, }, "auth0": { - Description: "Settings for Auth0 IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for Auth0 IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID from Auth0", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from Auth0", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "domain": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The domain of the Auth0 application", - Required: true, + Required: true, }, }, }, }, "azure": { - Description: "Settings for Azure IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for Azure IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from Azure", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "app_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Application ID from your Enterprise Application Properties in Azure AD", - Required: true, + Required: true, }, "tenant": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Azure tenant", - Optional: true, + Optional: true, }, "object_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Object ID from your Enterprise Application Properties in Azure AD", - Optional: true, + Optional: true, }, "autosync_teams_and_users": { - Type: schema.TypeBool, + Type: schema.TypeBool, Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", - Optional: true, - Default: false, + Optional: true, + Default: false, }, "sync_interval": { - Type: schema.TypeInt, + Type: schema.TypeInt, Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, + Optional: true, }, }, }, }, "onelogin": { - Description: "Settings for onelogin IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for onelogin IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID from Onelogin", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from Onelogin", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "domain": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The domain to be used for authentication", - Required: true, + Required: true, }, "app_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Codefresh application ID in your Onelogin", - Optional: true, + Optional: true, }, "api_client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, + Optional: true, }, "api_client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, - // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated + Optional: true, + // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated //DiffSuppressFunc: surpressObfuscatedFields(), }, }, }, }, "keycloak": { - Description: "Settings for Keycloak IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "client_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client ID from Keycloak", - Required: true, + Required: true, }, "client_secret": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Client secret from Keycloak", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "client_secret_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "host": { - Type: schema.TypeString, - Description: "The Keycloak URL", - Required: true, + Type: schema.TypeString, + Description: "The Keycloak URL", + Required: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), }, "realm": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", - Optional: true, - Default: "master", + Optional: true, + Default: "master", }, }, }, }, "saml": { - Description: "Settings for SAML IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Description: "Settings for SAML IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, ExactlyOneOf: supportedIdps, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "endpoint": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The SSO endpoint of your Identity Provider", - Required: true, + Required: true, }, "application_certificate": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, }, "application_certificate_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "provider": { - Type: schema.TypeString, - Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", - Optional: true, - Default: "", - ValidateFunc: validation.StringInSlice([]string{"","okta","GSuite"},false), + Type: schema.TypeString, + Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"", "okta", "GSuite"}, false), }, "allowed_groups_for_sync": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for GSuite only: Comma separated list of groups to sync", - Optional: true, + Optional: true, }, "autosync_teams_and_users": { - Type: schema.TypeBool, + Type: schema.TypeBool, Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", - Optional: true, - Default: false, + Optional: true, + Default: false, }, "sync_interval": { - Type: schema.TypeInt, + Type: schema.TypeInt, Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, + Optional: true, }, "activate_users_after_sync": { - Type: schema.TypeBool, + Type: schema.TypeBool, Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", - Optional: true, - Default: false, + Optional: true, + Default: false, }, "app_id": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for Okta only: The Codefresh application ID in Okta", - Optional: true, + Optional: true, }, "client_host": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", - Optional: true, + Optional: true, }, "json_keyfile": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", - Optional: true, + Optional: true, }, "json_keyfile_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "admin_email": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, + Optional: true, }, "admin_email_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, }, "access_token": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, + Optional: true, }, "access_token_encrypted": { - Type: schema.TypeString, + Type: schema.TypeString, Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, + Optional: true, + Computed: true, + }, + }, + }, + }, + "ldap": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Description: "ldap server url", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^ldap(s?):\/\/`), "must be a valid ldap url (must start with ldap:// or ldaps://)"), + }, + "password": { + Type: schema.TypeString, + Description: "The password of the user defined in Distinguished name that will be used to search other users", + Required: true, + Sensitive: true, + }, + "password_encrypted": { + Type: schema.TypeString, + Description: "Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "distinguished_name": { + Type: schema.TypeString, + Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", + Optional: true, + Computed: true, + }, + "search_base": { + Type: schema.TypeString, + Description: "The search-user scope in LDAP notation", + Required: true, + }, + "search_filter": { + Type: schema.TypeString, + Description: "The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName", + Optional: true, + }, + "certificate": { + Type: schema.TypeString, + Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", + Optional: true, + }, + "certificate_encrypted": { + Type: schema.TypeString, + Description: "Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", + Optional: true, + }, + "search_base_for_sync": { + Type: schema.TypeString, + Description: "Synchronize using a custom search base, by deafult seach_base is used", + Optional: true, }, }, }, @@ -607,7 +673,7 @@ func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - id,err := client.CreateIDP(mapResourceToIDP(d),d.Get("is_global").(bool)) + id, err := client.CreateIDP(mapResourceToIDP(d), d.Get("is_global").(bool)) if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) @@ -629,8 +695,7 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { if isGlobal { cfClientIDP, err = client.GetIdpByID(idpID) - } else - { + } else { cfClientIDP, err = client.GetAccountIdpByID(idpID) } @@ -641,7 +706,7 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { } log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) return err - + } err = mapIDPToResource(*cfClientIDP, d) @@ -680,8 +745,7 @@ func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) return err } - } else - { + } else { err = client.DeleteIDPAccount(d.Id()) if err != nil { @@ -719,17 +783,17 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "github" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, + "client_id": cfClientIDP.ClientId, // Codefresh API Returns the client secret as an encrypted string on the server side // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret // for Terraform to properly calculate the diff - "client_secret": d.Get("github.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "token_url": cfClientIDP.TokenURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_host": cfClientIDP.ApiHost, - "api_path_prefix": cfClientIDP.ApiPathPrefix, + "client_secret": d.Get("github.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "token_url": cfClientIDP.TokenURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_host": cfClientIDP.ApiHost, + "api_path_prefix": cfClientIDP.ApiPathPrefix, }} d.Set("github", attributes) @@ -737,12 +801,12 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "gitlab" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("gitlab.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_url": cfClientIDP.ApiURL, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("gitlab.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_url": cfClientIDP.ApiURL, }} d.Set("gitlab", attributes) @@ -750,15 +814,15 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "okta" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("okta.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "client_host": cfClientIDP.ClientHost, - "app_id": d.Get("okta.0.app_id"), - "app_id_encrypted": cfClientIDP.AppId, - "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, - "access_token": d.Get("okta.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("okta.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "client_host": cfClientIDP.ClientHost, + "app_id": d.Get("okta.0.app_id"), + "app_id_encrypted": cfClientIDP.AppId, + "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + "access_token": d.Get("okta.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, }} d.Set("okta", attributes) @@ -766,21 +830,21 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "google" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("google.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "admin_email": d.Get("google.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, - "json_keyfile": d.Get("google.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, - "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, - "sync_field": cfClientIDP.SyncField, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("google.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "admin_email": d.Get("google.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, + "json_keyfile": d.Get("google.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "sync_field": cfClientIDP.SyncField, }} // When account scoped, admin email is returned obfuscated after first apply, causing diff to appear everytime. // This behavior would always set the admin email from the resource, allowing at least changing the secret when the value in terraform configuration changes. // Though it would not detect drift if the secret is changed from UI. - if !isGlobal{ + if !isGlobal { attributes[0]["admin_email"] = d.Get("google.0.admin_email") } @@ -789,10 +853,10 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "auth0" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("auth0.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("auth0.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, }} d.Set("auth0", attributes) @@ -805,15 +869,15 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if err != nil { return err } - + attributes := []map[string]interface{}{{ - "app_id": cfClientIDP.ClientId, - "client_secret": d.Get("azure.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "object_id": cfClientIDP.AppId, - "autosync_teams_and_users": cfClientIDP.AutoGroupSync, - "sync_interval": syncInterval, - "tenant": cfClientIDP.Tenant, + "app_id": cfClientIDP.ClientId, + "client_secret": d.Get("azure.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "object_id": cfClientIDP.AppId, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "sync_interval": syncInterval, + "tenant": cfClientIDP.Tenant, }} d.Set("azure", attributes) @@ -821,19 +885,19 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "onelogin" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("onelogin.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, - "api_client_id": cfClientIDP.ApiClientId, - - "api_client_secret": cfClientIDP.ApiClientSecret, - "app_id": cfClientIDP.AppId, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("onelogin.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, + "api_client_id": cfClientIDP.ApiClientId, + + "api_client_secret": cfClientIDP.ApiClientSecret, + "app_id": cfClientIDP.AppId, }} // When account scoped, Client secret is returned obfuscated after first apply, causing diff to appear everytime. // This behavior would always set the API clint secret from the resource, allowing at least changing the secret when the value in terraform configuration changes. // Though it would not detect drift if the secret is changed from UI. - if !isGlobal{ + if !isGlobal { attributes[0]["api_client_secret"] = d.Get("onelogin.0.api_client_secret") } @@ -842,11 +906,11 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "keycloak" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("keycloak.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "host": cfClientIDP.Host, - "realm": cfClientIDP.Realm, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("keycloak.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "host": cfClientIDP.Host, + "realm": cfClientIDP.Realm, }} d.Set("keycloak", attributes) @@ -859,39 +923,56 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { return err } attributes := []map[string]interface{}{{ - "endpoint": cfClientIDP.EntryPoint, - "application_certificate": d.Get("saml.0.application_certificate"), - "application_certificate_encrypted": cfClientIDP.ApplicationCert, - "provider": cfClientIDP.SamlProvider, - "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, - "autosync_teams_and_users": cfClientIDP.AutoGroupSync, - "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, - "sync_interval": syncInterval, - "app_id": cfClientIDP.AppId, - "client_host": cfClientIDP.ClientHost, - "json_keyfile": d.Get("saml.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, - "admin_email": d.Get("saml.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, - "access_token": d.Get("saml.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "endpoint": cfClientIDP.EntryPoint, + "application_certificate": d.Get("saml.0.application_certificate"), + "application_certificate_encrypted": cfClientIDP.ApplicationCert, + "provider": cfClientIDP.SamlProvider, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, + "sync_interval": syncInterval, + "app_id": cfClientIDP.AppId, + "client_host": cfClientIDP.ClientHost, + "json_keyfile": d.Get("saml.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "admin_email": d.Get("saml.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, + "access_token": d.Get("saml.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, }} d.Set("saml", attributes) } + if cfClientIDP.ClientType == "ldap" { + attributes := []map[string]interface{}{{ + "url": cfClientIDP.Url, + "password": d.Get("ldap.0.password"), + "password_encrypted": cfClientIDP.Password, + "distinguished_name": cfClientIDP.DistinguishedName, + "search_base": cfClientIDP.SearchBase, + "search_filter": cfClientIDP.SearchFilter, + "certificate": d.Get("ldap.0.certificate"), + "certificate_encrypted": cfClientIDP.Certificate, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "search_base_for_sync": cfClientIDP.SearchBaseForSync, + }} + + d.Set("ldap", attributes) + } + return nil } func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP := &cfclient.IDP{ - ID: d.Id(), - DisplayName: d.Get("display_name").(string), - ClientName: d.Get("name").(string), - RedirectUrl: d.Get("redirect_url").(string), - RedirectUiUrl: d.Get("redirect_ui_url").(string), - LoginUrl: d.Get("login_url").(string), + ID: d.Id(), + DisplayName: d.Get("display_name").(string), + ClientName: d.Get("name").(string), + RedirectUrl: d.Get("redirect_url").(string), + RedirectUiUrl: d.Get("redirect_ui_url").(string), + LoginUrl: d.Get("login_url").(string), } if _, ok := d.GetOk("github"); ok { @@ -985,5 +1066,17 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Access_token = d.Get("saml.0.access_token").(string) } + if _, ok := d.GetOk("ldap"); ok { + cfClientIDP.ClientType = "ldap" + cfClientIDP.Url = d.Get("ldap.0.url").(string) + cfClientIDP.Password = d.Get("ldap.0.password").(string) + cfClientIDP.DistinguishedName = d.Get("ldap.0.distinguished_name").(string) + cfClientIDP.SearchBase = d.Get("ldap.0.search_base").(string) + cfClientIDP.SearchFilter = d.Get("ldap.0.search_filter").(string) + cfClientIDP.Certificate = d.Get("ldap.0.certificate").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("ldap.0.allowed_groups_for_sync").(string) + cfClientIDP.SearchBaseForSync = d.Get("ldap.0.search_base_for_sync").(string) + } + return cfClientIDP -} \ No newline at end of file +} diff --git a/templates/guides/development.md.tmpl b/templates/guides/development.md.tmpl index d1369fa7..07d0051d 100644 --- a/templates/guides/development.md.tmpl +++ b/templates/guides/development.md.tmpl @@ -21,7 +21,7 @@ Set the [developer overrides](https://developer.hashicorp.com/terraform/cli/conf # `~/.terraformrc (Windows: %APPDATA%/.terraformrc) provider_installation { dev_overrides { - "codefresh.io/codefresh" = "[REPLACE WITH GOPATH]/bin" + "codefresh-io/codefresh" = "[REPLACE WITH GOPATH]/bin" } direct {} } From 4c63ac8af68913ccb26877174cfd6501aac7e17c Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 15 Feb 2024 10:25:08 +0200 Subject: [PATCH 17/25] Split resources --- codefresh/provider.go | 1 + codefresh/resource_account_idp.go | 428 +++++++++ codefresh/resource_idp.go | 1288 ++++++++++++++-------------- codefresh/resource_idp_accounts.go | 16 +- codefresh/resource_idp_test.go | 81 ++ 5 files changed, 1139 insertions(+), 675 deletions(-) create mode 100644 codefresh/resource_account_idp.go create mode 100644 codefresh/resource_idp_test.go diff --git a/codefresh/provider.go b/codefresh/provider.go index 85eb230e..29a5b887 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -69,6 +69,7 @@ func Provider() *schema.Provider { "codefresh_team": resourceTeam(), "codefresh_abac_rules": resourceGitopsAbacRule(), "codefresh_idp": resourceIdp(), + "codefresh_account_idp": resourceAccountIdp(), }, ConfigureFunc: configureProvider, } diff --git a/codefresh/resource_account_idp.go b/codefresh/resource_account_idp.go new file mode 100644 index 00000000..7475fe8e --- /dev/null +++ b/codefresh/resource_account_idp.go @@ -0,0 +1,428 @@ +package codefresh + +import ( + "context" + "fmt" + "log" + "strconv" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceAccountIdp() *schema.Resource { + return &schema.Resource{ + Description: "Identity providers used in Codefresh for user authentication.", + Create: resourceAccountIDPCreate, + Read: resourceAccountIDPRead, + Update: resourceAccountIDPUpdate, + Delete: resourceAccountIDPDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + CustomizeDiff: customdiff.All( + // Recreate idp if the type has changed - we cannot simply do ForceNew on client_type as it is computed + customdiff.ForceNewIf("client_type", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool { + clientTypeInState := d.Get("client_type").(string) + attributesForIdpTypeInState := d.Get(clientTypeInState) + // If there is a different type of idp in the state, the idp needs to be recreated + if attributesForIdpTypeInState == nil { + d.SetNewComputed("client_type") + return true + } else if len(attributesForIdpTypeInState.([]interface{})) < 1 { + d.SetNewComputed("client_type") + return true + } else { + return false + } + }), + // If name has changed for an account scoped IDP the provider needs to ignore it as the API always generates the name + customdiff.If(func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { + return d.HasChange("name") + }, + func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + old, _ := d.GetChange("name") + if err := d.SetNew("name", old); err != nil { + return err + } + return nil + }), + ), + // Defined in resource_idp, as schema is the same for global and account scoped IDPs + Schema: idpSchema, + } +} + +func resourceAccountIDPCreate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + + id, err := client.CreateIDP(mapResourceToAccountIDP(d), false) + + if err != nil { + log.Printf("[DEBUG] Error while creating idp. Error = %v", err) + return err + } + + d.SetId(id) + return resourceIDPRead(d, meta) +} + +func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + idpID := d.Id() + + var cfClientIDP *cfclient.IDP + var err error + + + cfClientIDP, err = client.GetAccountIdpByID(idpID) + + + if err != nil { + if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { + d.SetId("") + return nil + } + log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) + return err + + } + + err = mapAccountIDPToResource(*cfClientIDP, d) + + if err != nil { + log.Printf("[DEBUG] Error while getting mapping response to IDP object. Error = %v", err) + return err + } + + return nil +} + +func resourceAccountIDPDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfclient.Client) + + err := client.DeleteIDPAccount(d.Id()) + + if err != nil { + log.Printf("[DEBUG] Error while deleting account level IDP. Error = %v", err) + return err + } + + return nil +} + +func resourceAccountIDPUpdate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + + err := client.UpdateIDP(mapResourceToAccountIDP(d), false) + + if err != nil { + log.Printf("[DEBUG] Error while updating idp. Error = %v", err) + return err + } + + return resourceIDPRead(d, meta) +} + +func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { + d.SetId(cfClientIDP.ID) + d.Set("display_name", cfClientIDP.DisplayName) + d.Set("name", cfClientIDP.ClientName) + d.Set("redirect_url", cfClientIDP.RedirectUrl) + d.Set("redirect_ui_url", cfClientIDP.RedirectUiUrl) + d.Set("login_url", cfClientIDP.LoginUrl) + d.Set("client_type", cfClientIDP.ClientType) + + if cfClientIDP.ClientType == "github" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + // Codefresh API Returns the client secret as an encrypted string on the server side + // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret + // for Terraform to properly calculate the diff + "client_secret": d.Get("github.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "token_url": cfClientIDP.TokenURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_host": cfClientIDP.ApiHost, + "api_path_prefix": cfClientIDP.ApiPathPrefix, + }} + + d.Set("github", attributes) + } + + if cfClientIDP.ClientType == "gitlab" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("gitlab.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "authentication_url": cfClientIDP.AuthURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_url": cfClientIDP.ApiURL, + }} + + d.Set("gitlab", attributes) + } + + if cfClientIDP.ClientType == "okta" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("okta.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "client_host": cfClientIDP.ClientHost, + "app_id": d.Get("okta.0.app_id"), + "app_id_encrypted": cfClientIDP.AppId, + "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + "access_token": d.Get("okta.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, + }} + + d.Set("okta", attributes) + } + + if cfClientIDP.ClientType == "google" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("google.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "admin_email": d.Get("google.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, + "json_keyfile": d.Get("google.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "sync_field": cfClientIDP.SyncField, + }} + + d.Set("google", attributes) + } + + if cfClientIDP.ClientType == "auth0" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("auth0.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, + }} + + d.Set("auth0", attributes) + } + + if cfClientIDP.ClientType == "azure" { + + syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) + + if err != nil { + return err + } + + attributes := []map[string]interface{}{{ + "app_id": cfClientIDP.ClientId, + "client_secret": d.Get("azure.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "object_id": cfClientIDP.AppId, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "sync_interval": syncInterval, + "tenant": cfClientIDP.Tenant, + }} + + d.Set("azure", attributes) + } + + if cfClientIDP.ClientType == "onelogin" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("onelogin.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "domain": cfClientIDP.ClientHost, + "api_client_id": cfClientIDP.ApiClientId, + // When account scoped, Client secret is returned obfuscated after first apply, causing diff to appear everytime. + // This behavior would always set the API clint secret from the resource, allowing at least changing the secret when the value in terraform configuration changes. + // Though it would not detect drift if the secret is changed from UI. + "api_client_secret": d.Get("onelogin.0.api_client_secret"), + "app_id": cfClientIDP.AppId, + }} + + d.Set("onelogin", attributes) + } + + if cfClientIDP.ClientType == "keycloak" { + attributes := []map[string]interface{}{{ + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("keycloak.0.client_secret"), + "client_secret_encrypted": cfClientIDP.ClientSecret, + "host": cfClientIDP.Host, + "realm": cfClientIDP.Realm, + }} + + d.Set("keycloak", attributes) + } + + if cfClientIDP.ClientType == "saml" { + syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) + + if err != nil { + return err + } + attributes := []map[string]interface{}{{ + "endpoint": cfClientIDP.EntryPoint, + "application_certificate": d.Get("saml.0.application_certificate"), + "application_certificate_encrypted": cfClientIDP.ApplicationCert, + "provider": cfClientIDP.SamlProvider, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, + "sync_interval": syncInterval, + "app_id": cfClientIDP.AppId, + "client_host": cfClientIDP.ClientHost, + "json_keyfile": d.Get("saml.0.json_keyfile"), + "json_keyfile_encrypted": cfClientIDP.KeyFile, + "admin_email": d.Get("saml.0.admin_email"), + "admin_email_encrypted": cfClientIDP.Subject, + "access_token": d.Get("saml.0.access_token"), + "access_token_encrypted": cfClientIDP.Access_token, + }} + + d.Set("saml", attributes) + } + + if cfClientIDP.ClientType == "ldap" { + attributes := []map[string]interface{}{{ + "url": cfClientIDP.Url, + "password": d.Get("ldap.0.password"), + "password_encrypted": cfClientIDP.Password, + "distinguished_name": cfClientIDP.DistinguishedName, + "search_base": cfClientIDP.SearchBase, + "search_filter": cfClientIDP.SearchFilter, + "certificate": d.Get("ldap.0.certificate"), + "certificate_encrypted": cfClientIDP.Certificate, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "search_base_for_sync": cfClientIDP.SearchBaseForSync, + }} + + d.Set("ldap", attributes) + } + + return nil +} + +func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { + + cfClientIDP := &cfclient.IDP{ + ID: d.Id(), + DisplayName: d.Get("display_name").(string), + ClientName: d.Get("name").(string), + RedirectUrl: d.Get("redirect_url").(string), + RedirectUiUrl: d.Get("redirect_ui_url").(string), + LoginUrl: d.Get("login_url").(string), + } + + if _, ok := d.GetOk("github"); ok { + cfClientIDP.ClientType = "github" + cfClientIDP.ClientId = d.Get("github.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) + cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) + cfClientIDP.TokenURL = d.Get("github.0.token_url").(string) + cfClientIDP.UserProfileURL = d.Get("github.0.user_profile_url").(string) + cfClientIDP.ApiHost = d.Get("github.0.api_host").(string) + cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) + } + + if _, ok := d.GetOk("gitlab"); ok { + cfClientIDP.ClientType = "gitlab" + cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) + cfClientIDP.AuthURL = d.Get("gitlab.0.authentication_url").(string) + cfClientIDP.UserProfileURL = d.Get("gitlab.0.user_profile_url").(string) + cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) + } + + if _, ok := d.GetOk("okta"); ok { + cfClientIDP.ClientType = "okta" + cfClientIDP.ClientId = d.Get("okta.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("okta.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("okta.0.client_host").(string) + cfClientIDP.AppId = d.Get("okta.0.app_id").(string) + cfClientIDP.SyncMirrorAccounts = datautil.ConvertStringArr(d.Get("okta.0.sync_mirror_accounts").([]interface{})) + cfClientIDP.Access_token = d.Get("okta.0.access_token").(string) + } + + if _, ok := d.GetOk("google"); ok { + cfClientIDP.ClientType = "google" + cfClientIDP.ClientId = d.Get("google.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("google.0.client_secret").(string) + cfClientIDP.KeyFile = d.Get("google.0.json_keyfile").(string) + cfClientIDP.Subject = d.Get("google.0.admin_email").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("google.0.allowed_groups_for_sync").(string) + cfClientIDP.SyncField = d.Get("google.0.sync_field").(string) + } + + if _, ok := d.GetOk("auth0"); ok { + cfClientIDP.ClientType = "auth0" + cfClientIDP.ClientId = d.Get("auth0.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("auth0.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("auth0.0.domain").(string) + } + + if _, ok := d.GetOk("azure"); ok { + cfClientIDP.ClientType = "azure" + cfClientIDP.ClientId = d.Get("azure.0.app_id").(string) + cfClientIDP.ClientSecret = d.Get("azure.0.client_secret").(string) + cfClientIDP.AppId = d.Get("azure.0.object_id").(string) + cfClientIDP.Tenant = d.Get("azure.0.tenant").(string) + cfClientIDP.AutoGroupSync = d.Get("azure.0.autosync_teams_and_users").(bool) + cfClientIDP.SyncInterval = strconv.Itoa(d.Get("azure.0.sync_interval").(int)) + } + + if _, ok := d.GetOk("onelogin"); ok { + cfClientIDP.ClientType = "onelogin" + cfClientIDP.ClientId = d.Get("onelogin.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("onelogin.0.client_secret").(string) + cfClientIDP.ClientHost = d.Get("onelogin.0.domain").(string) + cfClientIDP.AppId = d.Get("onelogin.0.app_id").(string) + cfClientIDP.ApiClientId = d.Get("onelogin.0.api_client_id").(string) + cfClientIDP.ApiClientSecret = d.Get("onelogin.0.api_client_secret").(string) + } + + if _, ok := d.GetOk("keycloak"); ok { + cfClientIDP.ClientType = "keycloak" + cfClientIDP.ClientId = d.Get("keycloak.0.client_id").(string) + cfClientIDP.ClientSecret = d.Get("keycloak.0.client_secret").(string) + cfClientIDP.Host = d.Get("keycloak.0.host").(string) + cfClientIDP.Realm = d.Get("keycloak.0.realm").(string) + } + + if _, ok := d.GetOk("saml"); ok { + cfClientIDP.ClientType = "saml" + cfClientIDP.SamlProvider = d.Get("saml.0.provider").(string) + cfClientIDP.EntryPoint = d.Get("saml.0.endpoint").(string) + cfClientIDP.ApplicationCert = d.Get("saml.0.application_certificate").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("saml.0.allowed_groups_for_sync").(string) + cfClientIDP.AutoGroupSync = d.Get("saml.0.autosync_teams_and_users").(bool) + cfClientIDP.ActivateUserAfterSync = d.Get("saml.0.activate_users_after_sync").(bool) + cfClientIDP.SyncInterval = strconv.Itoa(d.Get("saml.0.sync_interval").(int)) + cfClientIDP.AppId = d.Get("saml.0.app_id").(string) + cfClientIDP.ClientHost = d.Get("saml.0.client_host").(string) + cfClientIDP.KeyFile = d.Get("saml.0.json_keyfile").(string) + cfClientIDP.Subject = d.Get("saml.0.admin_email").(string) + cfClientIDP.Access_token = d.Get("saml.0.access_token").(string) + } + + if _, ok := d.GetOk("ldap"); ok { + cfClientIDP.ClientType = "ldap" + cfClientIDP.Url = d.Get("ldap.0.url").(string) + cfClientIDP.Password = d.Get("ldap.0.password").(string) + cfClientIDP.DistinguishedName = d.Get("ldap.0.distinguished_name").(string) + cfClientIDP.SearchBase = d.Get("ldap.0.search_base").(string) + cfClientIDP.SearchFilter = d.Get("ldap.0.search_filter").(string) + cfClientIDP.Certificate = d.Get("ldap.0.certificate").(string) + cfClientIDP.AllowedGroupsForSync = d.Get("ldap.0.allowed_groups_for_sync").(string) + cfClientIDP.SearchBaseForSync = d.Get("ldap.0.search_base_for_sync").(string) + } + + return cfClientIDP +} diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 403c4be1..7b2d0c34 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -16,6 +16,609 @@ import ( ) var supportedIdps = []string{"github", "gitlab", "okta", "google", "auth0", "azure", "onelogin", "keycloak", "saml", "ldap"} +var idpSchema = map[string]*schema.Schema{ + "display_name": { + Description: "The display name for the IDP.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "Name of the IDP, will be generated if not set", + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "client_type": { + Description: "Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc)", + Type: schema.TypeString, + Computed: true, + ForceNew: true, + }, + "redirect_url": { + Description: "API Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "redirect_ui_url": { + Description: "UI Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "login_url": { + Type: schema.TypeString, + Computed: true, + }, + "config_hash": { + Type: schema.TypeString, + Computed: true, + }, + "github": { + Description: "Settings for GitHub IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Github", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from GitHub", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "authentication_url": { + Type: schema.TypeString, + Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", + Optional: true, + Default: "https://github.com/login/oauth/authorize", + }, + "token_url": { + Type: schema.TypeString, + Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", + Optional: true, + Default: "https://github.com/login/oauth/access_token", + }, + "user_profile_url": { + Type: schema.TypeString, + Description: "GitHub user profile url, Defaults to https://api.github.com/user", + Optional: true, + Default: "https://api.github.com/user", + }, + "api_host": { + Type: schema.TypeString, + Description: "GitHub API host, Defaults to api.github.com", + Optional: true, + Default: "api.github.com", + }, + "api_path_prefix": { + Type: schema.TypeString, + Description: "GitHub API url path prefix, defaults to /", + Optional: true, + Default: "/", + }, + }, + }, + }, + "gitlab": { + Description: "Settings for GitLab IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Gitlab", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Gitlab", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "authentication_url": { + Type: schema.TypeString, + Description: "Authentication url, Defaults to https://gitlab.com", + Optional: true, + Default: "https://gitlab.com", + }, + "user_profile_url": { + Type: schema.TypeString, + Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", + Optional: true, + Default: "https://gitlab.com/api/v4/user", + }, + "api_url": { + Type: schema.TypeString, + Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", + Optional: true, + Default: "https://gitlab.com/api/v4/", + }, + }, + }, + }, + "okta": { + Description: "Settings for Okta IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Okta", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "The OKTA organization URL, for example, https://.okta.com", + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your OKTA organization", + Optional: true, + }, + "app_id_encrypted": { + Type: schema.TypeString, + Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "sync_mirror_accounts": { + Type: schema.TypeList, + Description: "The names of the additional Codefresh accounts to be synced from Okta", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "access_token": { + Type: schema.TypeString, + Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + "access_token_encrypted": { + Type: schema.TypeString, + Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + }, + }, + }, + "google": { + Description: "Settings for Google IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Google, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Google", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "admin_email_encrypted": { + Type: schema.TypeString, + Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "json_keyfile_encrypted": { + Type: schema.TypeString, + Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Comma separated list of groups to sync", + Optional: true, + }, + "sync_field": { + Type: schema.TypeString, + Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", + Optional: true, + }, + }, + }, + }, + "auth0": { + Description: "Settings for Auth0 IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Auth0", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Auth0", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain of the Auth0 application", + Required: true, + }, + }, + }, + }, + "azure": { + Description: "Settings for Azure IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Azure", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Application ID from your Enterprise Application Properties in Azure AD", + Required: true, + }, + "tenant": { + Type: schema.TypeString, + Description: "Azure tenant", + Optional: true, + }, + "object_id": { + Type: schema.TypeString, + Description: "The Object ID from your Enterprise Application Properties in Azure AD", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + }, + }, + }, + "onelogin": { + Description: "Settings for onelogin IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Onelogin", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Onelogin", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain to be used for authentication", + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your Onelogin", + Optional: true, + }, + "api_client_id": { + Type: schema.TypeString, + Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + }, + "api_client_secret": { + Type: schema.TypeString, + Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated + //DiffSuppressFunc: surpressObfuscatedFields(), + }, + }, + }, + }, + "keycloak": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Keycloak", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Keycloak", + Required: true, + Sensitive: true, + }, + "client_secret_encrypted": { + Type: schema.TypeString, + Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "host": { + Type: schema.TypeString, + Description: "The Keycloak URL", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), + }, + "realm": { + Type: schema.TypeString, + Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", + Optional: true, + Default: "master", + }, + }, + }, + }, + "saml": { + Description: "Settings for SAML IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint": { + Type: schema.TypeString, + Description: "The SSO endpoint of your Identity Provider", + Required: true, + }, + "application_certificate": { + Type: schema.TypeString, + Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", + Required: true, + Sensitive: true, + }, + "application_certificate_encrypted": { + Type: schema.TypeString, + Description: "Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "provider": { + Type: schema.TypeString, + Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"", "okta", "GSuite"}, false), + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Valid for GSuite only: Comma separated list of groups to sync", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + "activate_users_after_sync": { + Type: schema.TypeBool, + Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", + Optional: true, + Default: false, + }, + "app_id": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Codefresh application ID in Okta", + Optional: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", + Optional: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "json_keyfile_encrypted": { + Type: schema.TypeString, + Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "admin_email_encrypted": { + Type: schema.TypeString, + Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "access_token": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + "access_token_encrypted": { + Type: schema.TypeString, + Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + }, + }, + }, + "ldap": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: supportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Description: "ldap server url", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^ldap(s?):\/\/`), "must be a valid ldap url (must start with ldap:// or ldaps://)"), + }, + "password": { + Type: schema.TypeString, + Description: "The password of the user defined in Distinguished name that will be used to search other users", + Required: true, + Sensitive: true, + }, + "password_encrypted": { + Type: schema.TypeString, + Description: "Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "distinguished_name": { + Type: schema.TypeString, + Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", + Optional: true, + Computed: true, + }, + "search_base": { + Type: schema.TypeString, + Description: "The search-user scope in LDAP notation", + Required: true, + }, + "search_filter": { + Type: schema.TypeString, + Description: "The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName", + Optional: true, + }, + "certificate": { + Type: schema.TypeString, + Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", + Optional: true, + }, + "certificate_encrypted": { + Type: schema.TypeString, + Description: "Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", + Optional: true, + Computed: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", + Optional: true, + }, + "search_base_for_sync": { + Type: schema.TypeString, + Description: "Synchronize using a custom search base, by deafult seach_base is used", + Optional: true, + }, + }, + }, + }, +} func resourceIdp() *schema.Resource { return &schema.Resource{ @@ -43,629 +646,8 @@ func resourceIdp() *schema.Resource { return false } }), - // If name has changed for an account scoped IDP the provider needs to ignore it as the API always generates the name - customdiff.If(func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { - bIsGlobal := d.Get("is_global").(bool) - return !bIsGlobal - }, - func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { - old, _ := d.GetChange("name") - if err := d.SetNew("name", old); err != nil { - return err - } - return nil - }), ), - Schema: map[string]*schema.Schema{ - "is_global": { - Type: schema.TypeBool, - Description: "If set to true IDP will be created globally for the entire platform - this requires a platform admin token and is meant for on-prem installations of Codefresh. If false the IDP will be created at the level of a single account which is derived from the API token used. Defaults to false", - Optional: true, - Default: false, - ForceNew: true, - }, - "display_name": { - Description: "The display name for the IDP.", - Type: schema.TypeString, - Required: true, - }, - "name": { - Description: "Name of the IDP, will be generated if not set", - Type: schema.TypeString, - Computed: true, - Optional: true, - }, - "client_type": { - Description: "Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc)", - Type: schema.TypeString, - Computed: true, - ForceNew: true, - }, - "redirect_url": { - Description: "API Callback url for the identity provider", - Type: schema.TypeString, - Computed: true, - }, - "redirect_ui_url": { - Description: "UI Callback url for the identity provider", - Type: schema.TypeString, - Computed: true, - }, - "login_url": { - Type: schema.TypeString, - Computed: true, - }, - "config_hash": { - Type: schema.TypeString, - Computed: true, - }, - "github": { - Description: "Settings for GitHub IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Github", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from GitHub", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "authentication_url": { - Type: schema.TypeString, - Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", - Optional: true, - Default: "https://github.com/login/oauth/authorize", - }, - "token_url": { - Type: schema.TypeString, - Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", - Optional: true, - Default: "https://github.com/login/oauth/access_token", - }, - "user_profile_url": { - Type: schema.TypeString, - Description: "GitHub user profile url, Defaults to https://api.github.com/user", - Optional: true, - Default: "https://api.github.com/user", - }, - "api_host": { - Type: schema.TypeString, - Description: "GitHub API host, Defaults to api.github.com", - Optional: true, - Default: "api.github.com", - }, - "api_path_prefix": { - Type: schema.TypeString, - Description: "GitHub API url path prefix, defaults to /", - Optional: true, - Default: "/", - }, - }, - }, - }, - "gitlab": { - Description: "Settings for GitLab IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Gitlab", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Gitlab", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "authentication_url": { - Type: schema.TypeString, - Description: "Authentication url, Defaults to https://gitlab.com", - Optional: true, - Default: "https://gitlab.com", - }, - "user_profile_url": { - Type: schema.TypeString, - Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", - Optional: true, - Default: "https://gitlab.com/api/v4/user", - }, - "api_url": { - Type: schema.TypeString, - Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", - Optional: true, - Default: "https://gitlab.com/api/v4/", - }, - }, - }, - }, - "okta": { - Description: "Settings for Okta IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret in Okta", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "client_host": { - Type: schema.TypeString, - Description: "The OKTA organization URL, for example, https://.okta.com", - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), - Required: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Codefresh application ID in your OKTA organization", - Optional: true, - }, - "app_id_encrypted": { - Type: schema.TypeString, - Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "sync_mirror_accounts": { - Type: schema.TypeList, - Description: "The names of the additional Codefresh accounts to be synced from Okta", - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "access_token": { - Type: schema.TypeString, - Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, - }, - "access_token_encrypted": { - Type: schema.TypeString, - Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - }, - }, - }, - "google": { - Description: "Settings for Google IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID in Google, must be unique across all identity providers in Codefresh", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret in Google", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "admin_email": { - Type: schema.TypeString, - Description: "Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, - }, - "admin_email_encrypted": { - Type: schema.TypeString, - Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "json_keyfile": { - Type: schema.TypeString, - Description: "JSON keyfile for google service account used for synchronization", - Optional: true, - }, - "json_keyfile_encrypted": { - Type: schema.TypeString, - Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "Comma separated list of groups to sync", - Optional: true, - }, - "sync_field": { - Type: schema.TypeString, - Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", - Optional: true, - }, - }, - }, - }, - "auth0": { - Description: "Settings for Auth0 IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Auth0", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Auth0", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "domain": { - Type: schema.TypeString, - Description: "The domain of the Auth0 application", - Required: true, - }, - }, - }, - }, - "azure": { - Description: "Settings for Azure IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Azure", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Application ID from your Enterprise Application Properties in Azure AD", - Required: true, - }, - "tenant": { - Type: schema.TypeString, - Description: "Azure tenant", - Optional: true, - }, - "object_id": { - Type: schema.TypeString, - Description: "The Object ID from your Enterprise Application Properties in Azure AD", - Optional: true, - }, - "autosync_teams_and_users": { - Type: schema.TypeBool, - Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", - Optional: true, - Default: false, - }, - "sync_interval": { - Type: schema.TypeInt, - Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, - }, - }, - }, - }, - "onelogin": { - Description: "Settings for onelogin IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Onelogin", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Onelogin", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "domain": { - Type: schema.TypeString, - Description: "The domain to be used for authentication", - Required: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Codefresh application ID in your Onelogin", - Optional: true, - }, - "api_client_id": { - Type: schema.TypeString, - Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, - }, - "api_client_secret": { - Type: schema.TypeString, - Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, - // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated - //DiffSuppressFunc: surpressObfuscatedFields(), - }, - }, - }, - }, - "keycloak": { - Description: "Settings for Keycloak IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Keycloak", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Keycloak", - Required: true, - Sensitive: true, - }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "host": { - Type: schema.TypeString, - Description: "The Keycloak URL", - Required: true, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), - }, - "realm": { - Type: schema.TypeString, - Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", - Optional: true, - Default: "master", - }, - }, - }, - }, - "saml": { - Description: "Settings for SAML IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "endpoint": { - Type: schema.TypeString, - Description: "The SSO endpoint of your Identity Provider", - Required: true, - }, - "application_certificate": { - Type: schema.TypeString, - Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", - Required: true, - Sensitive: true, - }, - "application_certificate_encrypted": { - Type: schema.TypeString, - Description: "Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "provider": { - Type: schema.TypeString, - Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", - Optional: true, - Default: "", - ValidateFunc: validation.StringInSlice([]string{"", "okta", "GSuite"}, false), - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "Valid for GSuite only: Comma separated list of groups to sync", - Optional: true, - }, - "autosync_teams_and_users": { - Type: schema.TypeBool, - Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", - Optional: true, - Default: false, - }, - "sync_interval": { - Type: schema.TypeInt, - Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, - }, - "activate_users_after_sync": { - Type: schema.TypeBool, - Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", - Optional: true, - Default: false, - }, - "app_id": { - Type: schema.TypeString, - Description: "Valid for Okta only: The Codefresh application ID in Okta", - Optional: true, - }, - "client_host": { - Type: schema.TypeString, - Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", - Optional: true, - }, - "json_keyfile": { - Type: schema.TypeString, - Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", - Optional: true, - }, - "json_keyfile_encrypted": { - Type: schema.TypeString, - Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "admin_email": { - Type: schema.TypeString, - Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, - }, - "admin_email_encrypted": { - Type: schema.TypeString, - Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "access_token": { - Type: schema.TypeString, - Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, - }, - "access_token_encrypted": { - Type: schema.TypeString, - Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - }, - }, - }, - "ldap": { - Description: "Settings for Keycloak IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Description: "ldap server url", - Required: true, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^ldap(s?):\/\/`), "must be a valid ldap url (must start with ldap:// or ldaps://)"), - }, - "password": { - Type: schema.TypeString, - Description: "The password of the user defined in Distinguished name that will be used to search other users", - Required: true, - Sensitive: true, - }, - "password_encrypted": { - Type: schema.TypeString, - Description: "Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "distinguished_name": { - Type: schema.TypeString, - Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", - Optional: true, - Computed: true, - }, - "search_base": { - Type: schema.TypeString, - Description: "The search-user scope in LDAP notation", - Required: true, - }, - "search_filter": { - Type: schema.TypeString, - Description: "The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName", - Optional: true, - }, - "certificate": { - Type: schema.TypeString, - Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", - Optional: true, - }, - "certificate_encrypted": { - Type: schema.TypeString, - Description: "Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", - Optional: true, - }, - "search_base_for_sync": { - Type: schema.TypeString, - Description: "Synchronize using a custom search base, by deafult seach_base is used", - Optional: true, - }, - }, - }, - }, - }, + Schema: idpSchema, } } @@ -673,7 +655,7 @@ func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - id, err := client.CreateIDP(mapResourceToIDP(d), d.Get("is_global").(bool)) + id, err := client.CreateIDP(mapResourceToIDP(d), true) if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) @@ -688,16 +670,12 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) idpID := d.Id() - isGlobal := d.Get("is_global").(bool) var cfClientIDP *cfclient.IDP var err error - if isGlobal { - cfClientIDP, err = client.GetIdpByID(idpID) - } else { - cfClientIDP, err = client.GetAccountIdpByID(idpID) - } + + cfClientIDP, err = client.GetIdpByID(idpID) if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { @@ -722,36 +700,26 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) idpID := d.Id() - isGlobal := d.Get("is_global").(bool) var cfClientIDP *cfclient.IDP var err error - if isGlobal { - cfClientIDP, err = client.GetIdpByID(idpID) + cfClientIDP, err = client.GetIdpByID(idpID) - if err != nil { - log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) - return err - } - - if len(cfClientIDP.Accounts) < 1 { - return errors.New("It is not allowed to delete IDPs without any assigned accounts as they are considered global. Assign at least one account before deleting") - } + if err != nil { + log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) + return err + } - err = client.DeleteIDP(d.Id()) + if len(cfClientIDP.Accounts) < 1 { + return errors.New("It is not allowed to delete IDPs without any assigned accounts as they are considered global. Assign at least one account before deleting") + } - if err != nil { - log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) - return err - } - } else { - err = client.DeleteIDPAccount(d.Id()) + err = client.DeleteIDP(d.Id()) - if err != nil { - log.Printf("[DEBUG] Error while deleting account level IDP. Error = %v", err) - return err - } + if err != nil { + log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) + return err } return nil @@ -761,7 +729,7 @@ func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) - err := client.UpdateIDP(mapResourceToIDP(d), d.Get("is_global").(bool)) + err := client.UpdateIDP(mapResourceToIDP(d), true) if err != nil { log.Printf("[DEBUG] Error while updating idp. Error = %v", err) @@ -772,7 +740,6 @@ func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { } func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { - isGlobal := d.Get("is_global").(bool) d.SetId(cfClientIDP.ID) d.Set("display_name", cfClientIDP.DisplayName) d.Set("name", cfClientIDP.ClientName) @@ -833,7 +800,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), "client_secret_encrypted": cfClientIDP.ClientSecret, - "admin_email": d.Get("google.0.admin_email"), + "admin_email": cfClientIDP.Subject, "admin_email_encrypted": cfClientIDP.Subject, "json_keyfile": d.Get("google.0.json_keyfile"), "json_keyfile_encrypted": cfClientIDP.KeyFile, @@ -841,13 +808,6 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "sync_field": cfClientIDP.SyncField, }} - // When account scoped, admin email is returned obfuscated after first apply, causing diff to appear everytime. - // This behavior would always set the admin email from the resource, allowing at least changing the secret when the value in terraform configuration changes. - // Though it would not detect drift if the secret is changed from UI. - if !isGlobal { - attributes[0]["admin_email"] = d.Get("google.0.admin_email") - } - d.Set("google", attributes) } @@ -894,12 +854,6 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { "api_client_secret": cfClientIDP.ApiClientSecret, "app_id": cfClientIDP.AppId, }} - // When account scoped, Client secret is returned obfuscated after first apply, causing diff to appear everytime. - // This behavior would always set the API clint secret from the resource, allowing at least changing the secret when the value in terraform configuration changes. - // Though it would not detect drift if the secret is changed from UI. - if !isGlobal { - attributes[0]["api_client_secret"] = d.Get("onelogin.0.api_client_secret") - } d.Set("onelogin", attributes) } diff --git a/codefresh/resource_idp_accounts.go b/codefresh/resource_idp_accounts.go index 64f24d84..4509c1ee 100644 --- a/codefresh/resource_idp_accounts.go +++ b/codefresh/resource_idp_accounts.go @@ -12,10 +12,10 @@ func resourceIDPAccounts() *schema.Resource { This resource adds the list of provided account IDs to the IDP. Because of the current Codefresh API limitation it's impossible to remove account from IDP, thus deletion is not supported. `, - Create: resourceAccountIDPCreate, - Read: resourceAccountIDPRead, - Update: resourceAccountIDPUpdate, - Delete: resourceAccountIDPDelete, + Create: resourceIDPAccountsCreate, + Read: resourceIDPAccountsRead, + Update: resourceIDPAccountsUpdate, + Delete: resourceIDPAccountsDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -37,7 +37,7 @@ Because of the current Codefresh API limitation it's impossible to remove accoun } } -func resourceAccountIDPCreate(d *schema.ResourceData, meta interface{}) error { +func resourceIDPAccountsCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) @@ -59,7 +59,7 @@ func resourceAccountIDPCreate(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { +func resourceIDPAccountsRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) @@ -87,13 +87,13 @@ func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceAccountIDPDelete(_ *schema.ResourceData, _ interface{}) error { +func resourceIDPAccountsDelete(_ *schema.ResourceData, _ interface{}) error { // todo // warning message return nil } -func resourceAccountIDPUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceIDPAccountsUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) diff --git a/codefresh/resource_idp_test.go b/codefresh/resource_idp_test.go new file mode 100644 index 00000000..e9c63603 --- /dev/null +++ b/codefresh/resource_idp_test.go @@ -0,0 +1,81 @@ +package codefresh + +import ( + "fmt" + "testing" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestIDPCodefreshProject_AccountSpecific(t *testing.T) { + uniqueId := acctest.RandString(10) + resourceName := "codefresh_idp.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + //CheckDestroy: testAccCheckCodefreshProjectDestroy, + Steps: []resource.TestStep{ + { + Config: testIDPCodefreshProjectAccountSpecificConfig("onelogin", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshIDPAccountSpecficExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-onelogin-%s", uniqueId)), + ), + }, + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateVerify: true, + // }, + }, + }) +} + +func testAccCheckCodefreshIDPAccountSpecficExists(resource string) resource.TestCheckFunc { + return func(state *terraform.State) error { + rs, ok := state.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + idpID := rs.Primary.ID + + apiClient := testAccProvider.Meta().(*cfclient.Client) + _, err := apiClient.GetAccountIdpByID(idpID) + + if err != nil { + return fmt.Errorf("error fetching project with resource %s. %s", resource, err) + } + return nil + } +} + +func testIDPCodefreshProjectAccountSpecificConfig(idpType string, uniqueId string) string { + + idpResource := "" + + if idpType == "onelogin" { + idpResource = fmt.Sprintf(` + resource "codefresh_idp" "test" { + display_name = "tf-test-onelogin-%s" + + onelogin { + client_id = "onelogin-%s" + client_secret = "myoneloginsecret1" + domain = "myonelogindomain" + app_id = "myappid" + api_client_id = "myonelogindomain" + api_client_secret = "myapiclientsecret1" + } + }`, uniqueId, uniqueId) + } + + return idpResource +} \ No newline at end of file From c49ef8278f2c52cb30e6ceebc48800dfa0842adb Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 15 Feb 2024 11:44:04 +0200 Subject: [PATCH 18/25] fmt and adjust test --- codefresh/provider.go | 2 +- codefresh/resource_account_idp.go | 2 -- ...rce_idp_test.go => resource_account_idp_test.go} | 13 +++++++------ codefresh/resource_idp.go | 1 - 4 files changed, 8 insertions(+), 10 deletions(-) rename codefresh/{resource_idp_test.go => resource_account_idp_test.go} (83%) diff --git a/codefresh/provider.go b/codefresh/provider.go index 29a5b887..21a39fdd 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -69,7 +69,7 @@ func Provider() *schema.Provider { "codefresh_team": resourceTeam(), "codefresh_abac_rules": resourceGitopsAbacRule(), "codefresh_idp": resourceIdp(), - "codefresh_account_idp": resourceAccountIdp(), + "codefresh_account_idp": resourceAccountIdp(), }, ConfigureFunc: configureProvider, } diff --git a/codefresh/resource_account_idp.go b/codefresh/resource_account_idp.go index 7475fe8e..96d1efbe 100644 --- a/codefresh/resource_account_idp.go +++ b/codefresh/resource_account_idp.go @@ -78,9 +78,7 @@ func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { var cfClientIDP *cfclient.IDP var err error - cfClientIDP, err = client.GetAccountIdpByID(idpID) - if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { diff --git a/codefresh/resource_idp_test.go b/codefresh/resource_account_idp_test.go similarity index 83% rename from codefresh/resource_idp_test.go rename to codefresh/resource_account_idp_test.go index e9c63603..946b7aa7 100644 --- a/codefresh/resource_idp_test.go +++ b/codefresh/resource_account_idp_test.go @@ -10,9 +10,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestIDPCodefreshProject_AccountSpecific(t *testing.T) { +// Check create, update and delete of all supported IDP types +func TestAccountIDPCodefreshProject_AllSupportedTypes(t *testing.T) { uniqueId := acctest.RandString(10) - resourceName := "codefresh_idp.test" + resourceName := "codefresh_account_idp.test" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -22,7 +23,7 @@ func TestIDPCodefreshProject_AccountSpecific(t *testing.T) { { Config: testIDPCodefreshProjectAccountSpecificConfig("onelogin", uniqueId), Check: resource.ComposeTestCheckFunc( - testAccCheckCodefreshIDPAccountSpecficExists(resourceName), + testAccCheckCodefreshAccountIDPExists(resourceName), resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-onelogin-%s", uniqueId)), ), }, @@ -35,7 +36,7 @@ func TestIDPCodefreshProject_AccountSpecific(t *testing.T) { }) } -func testAccCheckCodefreshIDPAccountSpecficExists(resource string) resource.TestCheckFunc { +func testAccCheckCodefreshAccountIDPExists(resource string) resource.TestCheckFunc { return func(state *terraform.State) error { rs, ok := state.RootModule().Resources[resource] if !ok { @@ -63,7 +64,7 @@ func testIDPCodefreshProjectAccountSpecificConfig(idpType string, uniqueId strin if idpType == "onelogin" { idpResource = fmt.Sprintf(` - resource "codefresh_idp" "test" { + resource "codefresh_account_idp" "test" { display_name = "tf-test-onelogin-%s" onelogin { @@ -78,4 +79,4 @@ func testIDPCodefreshProjectAccountSpecificConfig(idpType string, uniqueId strin } return idpResource -} \ No newline at end of file +} diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 7b2d0c34..c540efad 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -674,7 +674,6 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { var cfClientIDP *cfclient.IDP var err error - cfClientIDP, err = client.GetIdpByID(idpID) if err != nil { From 9d6a6c241acdc29c3ba62010ce6244270776ca38 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 15 Feb 2024 17:22:28 +0200 Subject: [PATCH 19/25] sort our provideraddr for debugging --- codefresh/env.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codefresh/env.go b/codefresh/env.go index 3a53357b..02c6c969 100644 --- a/codefresh/env.go +++ b/codefresh/env.go @@ -8,5 +8,5 @@ const ( ENV_CODEFRESH_API_KEY = "CODEFRESH_API_KEY" DEFAULT_CODEFRESH_API_URL = "https://g.codefresh.io/api" DEFAULT_CODEFRESH_API2_URL = "https://g.codefresh.io/2.0/api/graphql" - DEFAULT_CODEFRESH_PLUGIN_ADDR = "registry.terraform.io/-/codefresh" + DEFAULT_CODEFRESH_PLUGIN_ADDR = "registry.terraform.io/codefresh-io/codefresh" ) diff --git a/main.go b/main.go index 39ca6237..ba1488ba 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ func main() { providerAddr = codefresh.DEFAULT_CODEFRESH_PLUGIN_ADDR } plugin.Serve(&plugin.ServeOpts{ - ProviderAddr: "codefresh-io/codefresh", // Required for debug attaching + ProviderAddr: providerAddr, // Required for debug attaching ProviderFunc: codefresh.Provider, Debug: debugMode, }) From 4a02dde15579829a6aa0de40619d9173bc1d130b Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Mon, 19 Feb 2024 16:41:06 +0200 Subject: [PATCH 20/25] add tests for multiple identity providers --- codefresh/resource_account_idp_test.go | 210 ++++++++++++++++++++++++- codefresh/resource_idp.go | 2 +- 2 files changed, 204 insertions(+), 8 deletions(-) diff --git a/codefresh/resource_account_idp_test.go b/codefresh/resource_account_idp_test.go index 946b7aa7..04612a3e 100644 --- a/codefresh/resource_account_idp_test.go +++ b/codefresh/resource_account_idp_test.go @@ -21,17 +21,75 @@ func TestAccountIDPCodefreshProject_AllSupportedTypes(t *testing.T) { //CheckDestroy: testAccCheckCodefreshProjectDestroy, Steps: []resource.TestStep{ { - Config: testIDPCodefreshProjectAccountSpecificConfig("onelogin", uniqueId), + Config: testAccountIDPCodefreshConfig("onelogin", uniqueId), Check: resource.ComposeTestCheckFunc( testAccCheckCodefreshAccountIDPExists(resourceName), resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-onelogin-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "onelogin"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + // For existing resources codefresh returns the secrets in encrypted format, to avoid constant diff we store those in _encrypted, hence on import the secrets will be empty + ImportStateVerifyIgnore: []string{"onelogin.0.client_secret"}, + }, + { + Config: testAccountIDPCodefreshConfig("auth0", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-auth0-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "auth0"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("azure", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-azure-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "azure"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("google", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-google-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "google"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("keycloak", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-keycloak-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "keycloak"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("ldap", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-ldap-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "ldap"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("okta", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-okta-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "okta"), + ), + }, + { + Config: testAccountIDPCodefreshConfig("saml", uniqueId), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAccountIDPExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", fmt.Sprintf("tf-test-saml-%s", uniqueId)), + resource.TestCheckResourceAttr(resourceName, "client_type", "saml"), ), }, - // { - // ResourceName: resourceName, - // ImportState: true, - // ImportStateVerify: true, - // }, }, }) } @@ -58,7 +116,7 @@ func testAccCheckCodefreshAccountIDPExists(resource string) resource.TestCheckFu } } -func testIDPCodefreshProjectAccountSpecificConfig(idpType string, uniqueId string) string { +func testAccountIDPCodefreshConfig(idpType string, uniqueId string) string { idpResource := "" @@ -78,5 +136,143 @@ func testIDPCodefreshProjectAccountSpecificConfig(idpType string, uniqueId strin }`, uniqueId, uniqueId) } + if idpType == "auth0" { + idpResource = fmt.Sprintf(` + resource "codefresh_account_idp" "test" { + display_name = "tf-test-auth0-%s" + name = "tf-auth0-test34" + auth0 { + client_id = "blah-auth0-%s" + client_secret = "asdddd" + domain = "codefresh.auth0.com" + } + }`, uniqueId, uniqueId) + } + + if idpType == "azure" { + idpResource = fmt.Sprintf(` + resource "codefresh_account_idp" "test" { + display_name = "tf-test-azure-%s" + name = "tf-azure-test" + azure { + app_id = "azure-codefresh-test-%s" + client_secret = "mysecret99" + object_id = "myobjectidtest" + autosync_teams_and_users = true + sync_interval = 7 + } + }`, uniqueId, uniqueId) + } + + if idpType == "google" { + idpResource = fmt.Sprintf(` + resource "codefresh_account_idp" "test" { + display_name = "tf-test-google-%s" + name = "tf-google-test" + google { + client_id = "tf-test-google-%s" + client_secret = "mysecret99" + admin_email = "admin@codefresh.io" + sync_field = "myfield" + json_keyfile = < Date: Mon, 19 Feb 2024 18:12:11 +0200 Subject: [PATCH 21/25] update docs --- docs/guides/development.md | 2 +- docs/resources/account_idp.md | 225 ++++++++++++++++++++++++++++++++++ docs/resources/idp.md | 225 ++++++++++++++++++++++++++++++++++ 3 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 docs/resources/account_idp.md create mode 100644 docs/resources/idp.md diff --git a/docs/guides/development.md b/docs/guides/development.md index d1369fa7..07d0051d 100644 --- a/docs/guides/development.md +++ b/docs/guides/development.md @@ -21,7 +21,7 @@ Set the [developer overrides](https://developer.hashicorp.com/terraform/cli/conf # `~/.terraformrc (Windows: %APPDATA%/.terraformrc) provider_installation { dev_overrides { - "codefresh.io/codefresh" = "[REPLACE WITH GOPATH]/bin" + "codefresh-io/codefresh" = "[REPLACE WITH GOPATH]/bin" } direct {} } diff --git a/docs/resources/account_idp.md b/docs/resources/account_idp.md new file mode 100644 index 00000000..538d98ec --- /dev/null +++ b/docs/resources/account_idp.md @@ -0,0 +1,225 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "codefresh_account_idp Resource - terraform-provider-codefresh" +subcategory: "" +description: |- + Identity providers used in Codefresh for user authentication. +--- + +# codefresh_account_idp (Resource) + +Identity providers used in Codefresh for user authentication. + + + + +## Schema + +### Required + +- `display_name` (String) The display name for the IDP. + +### Optional + +- `auth0` (Block List, Max: 1) Settings for Auth0 IDP (see [below for nested schema](#nestedblock--auth0)) +- `azure` (Block List, Max: 1) Settings for Azure IDP (see [below for nested schema](#nestedblock--azure)) +- `github` (Block List, Max: 1) Settings for GitHub IDP (see [below for nested schema](#nestedblock--github)) +- `gitlab` (Block List, Max: 1) Settings for GitLab IDP (see [below for nested schema](#nestedblock--gitlab)) +- `google` (Block List, Max: 1) Settings for Google IDP (see [below for nested schema](#nestedblock--google)) +- `keycloak` (Block List, Max: 1) Settings for Keycloak IDP (see [below for nested schema](#nestedblock--keycloak)) +- `ldap` (Block List, Max: 1) Settings for Keycloak IDP (see [below for nested schema](#nestedblock--ldap)) +- `name` (String) Name of the IDP, will be generated if not set +- `okta` (Block List, Max: 1) Settings for Okta IDP (see [below for nested schema](#nestedblock--okta)) +- `onelogin` (Block List, Max: 1) Settings for onelogin IDP (see [below for nested schema](#nestedblock--onelogin)) +- `saml` (Block List, Max: 1) Settings for SAML IDP (see [below for nested schema](#nestedblock--saml)) + +### Read-Only + +- `client_type` (String) Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc) +- `config_hash` (String) +- `id` (String) The ID of this resource. +- `login_url` (String) +- `redirect_ui_url` (String) UI Callback url for the identity provider +- `redirect_url` (String) API Callback url for the identity provider + + +### Nested Schema for `auth0` + +Required: + +- `client_id` (String) Client ID from Auth0 +- `client_secret` (String, Sensitive) Client secret from Auth0 +- `domain` (String) The domain of the Auth0 application + +Optional: + +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value + + + +### Nested Schema for `azure` + +Required: + +- `app_id` (String) The Application ID from your Enterprise Application Properties in Azure AD +- `client_secret` (String, Sensitive) Client secret from Azure + +Optional: + +- `autosync_teams_and_users` (Boolean) Set to true to sync user accounts in Azure AD to your Codefresh account +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `object_id` (String) The Object ID from your Enterprise Application Properties in Azure AD +- `sync_interval` (Number) Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours +- `tenant` (String) Azure tenant + + + +### Nested Schema for `github` + +Required: + +- `client_id` (String) Client ID from Github +- `client_secret` (String, Sensitive) Client secret from GitHub + +Optional: + +- `api_host` (String) GitHub API host, Defaults to api.github.com +- `api_path_prefix` (String) GitHub API url path prefix, defaults to / +- `authentication_url` (String) Authentication url, Defaults to https://github.com/login/oauth/authorize +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `token_url` (String) GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token +- `user_profile_url` (String) GitHub user profile url, Defaults to https://api.github.com/user + + + +### Nested Schema for `gitlab` + +Required: + +- `client_id` (String) Client ID from Gitlab +- `client_secret` (String, Sensitive) Client secret from Gitlab + +Optional: + +- `api_url` (String) Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/ +- `authentication_url` (String) Authentication url, Defaults to https://gitlab.com +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `user_profile_url` (String) User profile url, Defaults to https://gitlab.com/api/v4/user + + + +### Nested Schema for `google` + +Required: + +- `client_id` (String) Client ID in Google, must be unique across all identity providers in Codefresh +- `client_secret` (String, Sensitive) Client secret in Google + +Optional: + +- `admin_email` (String) Email of a user with admin permissions on google, relevant only for synchronization +- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `allowed_groups_for_sync` (String) Comma separated list of groups to sync +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `json_keyfile` (String) JSON keyfile for google service account used for synchronization +- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `sync_field` (String) Relevant for custom schema-based synchronization only. See Codefresh documentation + + + +### Nested Schema for `keycloak` + +Required: + +- `client_id` (String) Client ID from Keycloak +- `client_secret` (String, Sensitive) Client secret from Keycloak +- `host` (String) The Keycloak URL + +Optional: + +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `realm` (String) The Realm ID for Codefresh in Keycloak. Defaults to master + + + +### Nested Schema for `ldap` + +Required: + +- `password` (String, Sensitive) The password of the user defined in Distinguished name that will be used to search other users +- `search_base` (String) The search-user scope in LDAP notation +- `url` (String) ldap server url + +Optional: + +- `allowed_groups_for_sync` (String) To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced +- `certificate` (String) For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding +- `certificate_encrypted` (String) Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `distinguished_name` (String) The username to be used to search other users in LDAP notation (combination of cn, ou,dc) +- `password_encrypted` (String) Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `search_base_for_sync` (String) Synchronize using a custom search base, by deafult seach_base is used +- `search_filter` (String) The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName + + + +### Nested Schema for `okta` + +Required: + +- `client_host` (String) The OKTA organization URL, for example, https://.okta.com +- `client_id` (String) Client ID in Okta, must be unique across all identity providers in Codefresh +- `client_secret` (String, Sensitive) Client secret in Okta + +Optional: + +- `access_token` (String) The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh +- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `app_id` (String) The Codefresh application ID in your OKTA organization +- `app_id_encrypted` (String) Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `sync_mirror_accounts` (List of String) The names of the additional Codefresh accounts to be synced from Okta + + + +### Nested Schema for `onelogin` + +Required: + +- `client_id` (String) Client ID from Onelogin +- `client_secret` (String, Sensitive) Client secret from Onelogin +- `domain` (String) The domain to be used for authentication + +Optional: + +- `api_client_id` (String) Client ID for onelogin API, only needed if syncing users and groups from Onelogin +- `api_client_secret` (String) Client secret for onelogin API, only needed if syncing users and groups from Onelogin +- `app_id` (String) The Codefresh application ID in your Onelogin +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value + + + +### Nested Schema for `saml` + +Required: + +- `application_certificate` (String, Sensitive) The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand +- `endpoint` (String) The SSO endpoint of your Identity Provider + +Optional: + +- `access_token` (String) Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh +- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `activate_users_after_sync` (Boolean) Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false +- `admin_email` (String) Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization +- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `allowed_groups_for_sync` (String) Valid for GSuite only: Comma separated list of groups to sync +- `app_id` (String) Valid for Okta only: The Codefresh application ID in Okta +- `application_certificate_encrypted` (String) Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `autosync_teams_and_users` (Boolean) Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account +- `client_host` (String) Valid for Okta only: OKTA organization URL, for example, https://.okta.com +- `json_keyfile` (String) Valid for GSuite only: JSON keyfile for google service account used for synchronization +- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `provider` (String) SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string +- `sync_interval` (Number) Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours + + diff --git a/docs/resources/idp.md b/docs/resources/idp.md new file mode 100644 index 00000000..5af4f672 --- /dev/null +++ b/docs/resources/idp.md @@ -0,0 +1,225 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "codefresh_idp Resource - terraform-provider-codefresh" +subcategory: "" +description: |- + Identity providers used in Codefresh for user authentication. +--- + +# codefresh_idp (Resource) + +Identity providers used in Codefresh for user authentication. + + + + +## Schema + +### Required + +- `display_name` (String) The display name for the IDP. + +### Optional + +- `auth0` (Block List, Max: 1) Settings for Auth0 IDP (see [below for nested schema](#nestedblock--auth0)) +- `azure` (Block List, Max: 1) Settings for Azure IDP (see [below for nested schema](#nestedblock--azure)) +- `github` (Block List, Max: 1) Settings for GitHub IDP (see [below for nested schema](#nestedblock--github)) +- `gitlab` (Block List, Max: 1) Settings for GitLab IDP (see [below for nested schema](#nestedblock--gitlab)) +- `google` (Block List, Max: 1) Settings for Google IDP (see [below for nested schema](#nestedblock--google)) +- `keycloak` (Block List, Max: 1) Settings for Keycloak IDP (see [below for nested schema](#nestedblock--keycloak)) +- `ldap` (Block List, Max: 1) Settings for Keycloak IDP (see [below for nested schema](#nestedblock--ldap)) +- `name` (String) Name of the IDP, will be generated if not set +- `okta` (Block List, Max: 1) Settings for Okta IDP (see [below for nested schema](#nestedblock--okta)) +- `onelogin` (Block List, Max: 1) Settings for onelogin IDP (see [below for nested schema](#nestedblock--onelogin)) +- `saml` (Block List, Max: 1) Settings for SAML IDP (see [below for nested schema](#nestedblock--saml)) + +### Read-Only + +- `client_type` (String) Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc) +- `config_hash` (String) +- `id` (String) The ID of this resource. +- `login_url` (String) +- `redirect_ui_url` (String) UI Callback url for the identity provider +- `redirect_url` (String) API Callback url for the identity provider + + +### Nested Schema for `auth0` + +Required: + +- `client_id` (String) Client ID from Auth0 +- `client_secret` (String, Sensitive) Client secret from Auth0 +- `domain` (String) The domain of the Auth0 application + +Optional: + +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value + + + +### Nested Schema for `azure` + +Required: + +- `app_id` (String) The Application ID from your Enterprise Application Properties in Azure AD +- `client_secret` (String, Sensitive) Client secret from Azure + +Optional: + +- `autosync_teams_and_users` (Boolean) Set to true to sync user accounts in Azure AD to your Codefresh account +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `object_id` (String) The Object ID from your Enterprise Application Properties in Azure AD +- `sync_interval` (Number) Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours +- `tenant` (String) Azure tenant + + + +### Nested Schema for `github` + +Required: + +- `client_id` (String) Client ID from Github +- `client_secret` (String, Sensitive) Client secret from GitHub + +Optional: + +- `api_host` (String) GitHub API host, Defaults to api.github.com +- `api_path_prefix` (String) GitHub API url path prefix, defaults to / +- `authentication_url` (String) Authentication url, Defaults to https://github.com/login/oauth/authorize +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `token_url` (String) GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token +- `user_profile_url` (String) GitHub user profile url, Defaults to https://api.github.com/user + + + +### Nested Schema for `gitlab` + +Required: + +- `client_id` (String) Client ID from Gitlab +- `client_secret` (String, Sensitive) Client secret from Gitlab + +Optional: + +- `api_url` (String) Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/ +- `authentication_url` (String) Authentication url, Defaults to https://gitlab.com +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `user_profile_url` (String) User profile url, Defaults to https://gitlab.com/api/v4/user + + + +### Nested Schema for `google` + +Required: + +- `client_id` (String) Client ID in Google, must be unique across all identity providers in Codefresh +- `client_secret` (String, Sensitive) Client secret in Google + +Optional: + +- `admin_email` (String) Email of a user with admin permissions on google, relevant only for synchronization +- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `allowed_groups_for_sync` (String) Comma separated list of groups to sync +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `json_keyfile` (String) JSON keyfile for google service account used for synchronization +- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `sync_field` (String) Relevant for custom schema-based synchronization only. See Codefresh documentation + + + +### Nested Schema for `keycloak` + +Required: + +- `client_id` (String) Client ID from Keycloak +- `client_secret` (String, Sensitive) Client secret from Keycloak +- `host` (String) The Keycloak URL + +Optional: + +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `realm` (String) The Realm ID for Codefresh in Keycloak. Defaults to master + + + +### Nested Schema for `ldap` + +Required: + +- `password` (String, Sensitive) The password of the user defined in Distinguished name that will be used to search other users +- `search_base` (String) The search-user scope in LDAP notation +- `url` (String) ldap server url + +Optional: + +- `allowed_groups_for_sync` (String) To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced +- `certificate` (String) For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding +- `certificate_encrypted` (String) Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `distinguished_name` (String) The username to be used to search other users in LDAP notation (combination of cn, ou,dc) +- `password_encrypted` (String) Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `search_base_for_sync` (String) Synchronize using a custom search base, by deafult seach_base is used +- `search_filter` (String) The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName + + + +### Nested Schema for `okta` + +Required: + +- `client_host` (String) The OKTA organization URL, for example, https://.okta.com +- `client_id` (String) Client ID in Okta, must be unique across all identity providers in Codefresh +- `client_secret` (String, Sensitive) Client secret in Okta + +Optional: + +- `access_token` (String) The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh +- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `app_id` (String) The Codefresh application ID in your OKTA organization +- `app_id_encrypted` (String) Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `sync_mirror_accounts` (List of String) The names of the additional Codefresh accounts to be synced from Okta + + + +### Nested Schema for `onelogin` + +Required: + +- `client_id` (String) Client ID from Onelogin +- `client_secret` (String, Sensitive) Client secret from Onelogin +- `domain` (String) The domain to be used for authentication + +Optional: + +- `api_client_id` (String) Client ID for onelogin API, only needed if syncing users and groups from Onelogin +- `api_client_secret` (String) Client secret for onelogin API, only needed if syncing users and groups from Onelogin +- `app_id` (String) The Codefresh application ID in your Onelogin +- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value + + + +### Nested Schema for `saml` + +Required: + +- `application_certificate` (String, Sensitive) The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand +- `endpoint` (String) The SSO endpoint of your Identity Provider + +Optional: + +- `access_token` (String) Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh +- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `activate_users_after_sync` (Boolean) Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false +- `admin_email` (String) Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization +- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `allowed_groups_for_sync` (String) Valid for GSuite only: Comma separated list of groups to sync +- `app_id` (String) Valid for Okta only: The Codefresh application ID in Okta +- `application_certificate_encrypted` (String) Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `autosync_teams_and_users` (Boolean) Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account +- `client_host` (String) Valid for Okta only: OKTA organization URL, for example, https://.okta.com +- `json_keyfile` (String) Valid for GSuite only: JSON keyfile for google service account used for synchronization +- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value +- `provider` (String) SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string +- `sync_interval` (Number) Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours + + From 0302193b1c22da6bb3cd5c69883d927441903b90 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 20 Feb 2024 19:24:33 +0200 Subject: [PATCH 22/25] change description --- codefresh/resource_idp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 2e4a92fc..d04b2ef6 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -29,7 +29,7 @@ var idpSchema = map[string]*schema.Schema{ Optional: true, }, "client_type": { - Description: "Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc)", + Description: "Type of the IDP. Derived from idp specific config object (github, gitlab etc)", Type: schema.TypeString, Computed: true, ForceNew: true, From a5d8706f9bccd1204b71699dd2471016c3f5d86f Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 21 Feb 2024 14:57:49 +0200 Subject: [PATCH 23/25] add datasource for account idp and update docs --- codefresh/data_account_idp.go | 101 +++++++++++ codefresh/provider.go | 1 + codefresh/resource_account_idp.go | 100 +++++------ codefresh/resource_account_idp_test.go | 4 +- codefresh/resource_idp.go | 217 +++++------------------- docs/data-sources/account_idp.md | 32 ++++ docs/resources/account_idp.md | 76 +++++---- docs/resources/idp.md | 76 +++++---- templates/resources/account_idp.md.tmpl | 57 +++++++ templates/resources/idp.md.tmpl | 57 +++++++ 10 files changed, 429 insertions(+), 292 deletions(-) create mode 100644 codefresh/data_account_idp.go create mode 100644 docs/data-sources/account_idp.md create mode 100644 templates/resources/account_idp.md.tmpl create mode 100644 templates/resources/idp.md.tmpl diff --git a/codefresh/data_account_idp.go b/codefresh/data_account_idp.go new file mode 100644 index 00000000..21bfe159 --- /dev/null +++ b/codefresh/data_account_idp.go @@ -0,0 +1,101 @@ +package codefresh + +import ( + "fmt" + + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAccountIdp() *schema.Resource { + return &schema.Resource{ + Description: "This data source retrieves an account level identity provider", + Read: dataSourceAccountIdpRead, + Schema: AccountIdpSchema(), + } +} + +// IdpSchema - +func AccountIdpSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "_id": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"_id", "client_name"}, + }, + "client_name": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"_id", "client_name"}, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + "client_type": { + Type: schema.TypeString, + Computed: true, + }, + "redirect_url": { + Description: "API Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "redirect_ui_url": { + Description: "UI Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "login_url": { + Description: "Login url using the IDP to Codefresh", + Type: schema.TypeString, + Computed: true, + }, + } +} + +func dataSourceAccountIdpRead(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfclient.Client) + + idps, err := client.GetAccountIDPs() + if err != nil { + return err + } + + _id, _idOk := d.GetOk("_id") + clientName, clientNameOk := d.GetOk("client_name") + + for _, idp := range *idps { + if clientNameOk && clientName.(string) != idp.ClientName { + continue + } + if _idOk && _id.(string) != idp.ID { + continue + } + + err = mapDataAccountIdpToResource(idp, d) + if err != nil { + return err + } + } + + if d.Id() == "" { + return fmt.Errorf("[EROOR] Idp wasn't found") + } + + return nil +} + +func mapDataAccountIdpToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { + + d.SetId(cfClientIDP.ID) + d.Set("client_name", cfClientIDP.ClientName) + d.Set("client_type", cfClientIDP.ClientType) + d.Set("display_name", cfClientIDP.DisplayName) + d.Set("redirect_url", cfClientIDP.RedirectUrl) + d.Set("redirect_ui_url", cfClientIDP.RedirectUiUrl) + d.Set("login_url", cfClientIDP.LoginUrl) + + return nil +} diff --git a/codefresh/provider.go b/codefresh/provider.go index 21a39fdd..704a20f2 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -51,6 +51,7 @@ func Provider() *schema.Provider { "codefresh_users": dataSourceUsers(), "codefresh_registry": dataSourceRegistry(), "codefresh_pipelines": dataSourcePipelines(), + "codefresh_account_idp": dataSourceAccountIdp(), }, ResourcesMap: map[string]*schema.Resource{ "codefresh_account": resourceAccount(), diff --git a/codefresh/resource_account_idp.go b/codefresh/resource_account_idp.go index 96d1efbe..fc0267c6 100644 --- a/codefresh/resource_account_idp.go +++ b/codefresh/resource_account_idp.go @@ -14,7 +14,7 @@ import ( func resourceAccountIdp() *schema.Resource { return &schema.Resource{ - Description: "Identity providers used in Codefresh for user authentication.", + Description: "Account level identity providers", Create: resourceAccountIDPCreate, Read: resourceAccountIDPRead, Update: resourceAccountIDPUpdate, @@ -142,13 +142,12 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e // Codefresh API Returns the client secret as an encrypted string on the server side // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret // for Terraform to properly calculate the diff - "client_secret": d.Get("github.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "token_url": cfClientIDP.TokenURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_host": cfClientIDP.ApiHost, - "api_path_prefix": cfClientIDP.ApiPathPrefix, + "client_secret": d.Get("github.0.client_secret"), + "authentication_url": cfClientIDP.AuthURL, + "token_url": cfClientIDP.TokenURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_host": cfClientIDP.ApiHost, + "api_path_prefix": cfClientIDP.ApiPathPrefix, }} d.Set("github", attributes) @@ -156,12 +155,11 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e if cfClientIDP.ClientType == "gitlab" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("gitlab.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_url": cfClientIDP.ApiURL, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("gitlab.0.client_secret"), + "authentication_url": cfClientIDP.AuthURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_url": cfClientIDP.ApiURL, }} d.Set("gitlab", attributes) @@ -169,15 +167,12 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e if cfClientIDP.ClientType == "okta" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("okta.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "client_host": cfClientIDP.ClientHost, - "app_id": d.Get("okta.0.app_id"), - "app_id_encrypted": cfClientIDP.AppId, - "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, - "access_token": d.Get("okta.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("okta.0.client_secret"), + "client_host": cfClientIDP.ClientHost, + "app_id": d.Get("okta.0.app_id"), + "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + "access_token": d.Get("okta.0.access_token"), }} d.Set("okta", attributes) @@ -187,11 +182,8 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, "admin_email": d.Get("google.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, "json_keyfile": d.Get("google.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, "sync_field": cfClientIDP.SyncField, }} @@ -201,10 +193,9 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e if cfClientIDP.ClientType == "auth0" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("auth0.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("auth0.0.client_secret"), + "domain": cfClientIDP.ClientHost, }} d.Set("auth0", attributes) @@ -221,7 +212,6 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e attributes := []map[string]interface{}{{ "app_id": cfClientIDP.ClientId, "client_secret": d.Get("azure.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, "object_id": cfClientIDP.AppId, "autosync_teams_and_users": cfClientIDP.AutoGroupSync, "sync_interval": syncInterval, @@ -233,11 +223,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e if cfClientIDP.ClientType == "onelogin" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("onelogin.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, - "api_client_id": cfClientIDP.ApiClientId, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("onelogin.0.client_secret"), + "domain": cfClientIDP.ClientHost, + "api_client_id": cfClientIDP.ApiClientId, // When account scoped, Client secret is returned obfuscated after first apply, causing diff to appear everytime. // This behavior would always set the API clint secret from the resource, allowing at least changing the secret when the value in terraform configuration changes. // Though it would not detect drift if the secret is changed from UI. @@ -250,11 +239,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e if cfClientIDP.ClientType == "keycloak" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("keycloak.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "host": cfClientIDP.Host, - "realm": cfClientIDP.Realm, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("keycloak.0.client_secret"), + "host": cfClientIDP.Host, + "realm": cfClientIDP.Realm, }} d.Set("keycloak", attributes) @@ -267,22 +255,18 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e return err } attributes := []map[string]interface{}{{ - "endpoint": cfClientIDP.EntryPoint, - "application_certificate": d.Get("saml.0.application_certificate"), - "application_certificate_encrypted": cfClientIDP.ApplicationCert, - "provider": cfClientIDP.SamlProvider, - "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, - "autosync_teams_and_users": cfClientIDP.AutoGroupSync, - "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, - "sync_interval": syncInterval, - "app_id": cfClientIDP.AppId, - "client_host": cfClientIDP.ClientHost, - "json_keyfile": d.Get("saml.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, - "admin_email": d.Get("saml.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, - "access_token": d.Get("saml.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "endpoint": cfClientIDP.EntryPoint, + "application_certificate": d.Get("saml.0.application_certificate"), + "provider": cfClientIDP.SamlProvider, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, + "sync_interval": syncInterval, + "app_id": cfClientIDP.AppId, + "client_host": cfClientIDP.ClientHost, + "json_keyfile": d.Get("saml.0.json_keyfile"), + "admin_email": d.Get("saml.0.admin_email"), + "access_token": d.Get("saml.0.access_token"), }} d.Set("saml", attributes) @@ -292,12 +276,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e attributes := []map[string]interface{}{{ "url": cfClientIDP.Url, "password": d.Get("ldap.0.password"), - "password_encrypted": cfClientIDP.Password, "distinguished_name": cfClientIDP.DistinguishedName, "search_base": cfClientIDP.SearchBase, "search_filter": cfClientIDP.SearchFilter, "certificate": d.Get("ldap.0.certificate"), - "certificate_encrypted": cfClientIDP.Certificate, "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, "search_base_for_sync": cfClientIDP.SearchBaseForSync, }} diff --git a/codefresh/resource_account_idp_test.go b/codefresh/resource_account_idp_test.go index 04612a3e..7aca71f5 100644 --- a/codefresh/resource_account_idp_test.go +++ b/codefresh/resource_account_idp_test.go @@ -242,7 +242,7 @@ func testAccountIDPCodefreshConfig(idpType string, uniqueId string) string { app_id = "test1" access_token = "myaccesstoken1" } - }`, uniqueId,uniqueId) + }`, uniqueId, uniqueId) } if idpType == "saml" { @@ -271,7 +271,7 @@ func testAccountIDPCodefreshConfig(idpType string, uniqueId string) string { -----END CERTIFICATE----- EOT } - }`, uniqueId,uniqueId) + }`, uniqueId, uniqueId) } return idpResource diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index d04b2ef6..994cac9c 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -45,12 +45,9 @@ var idpSchema = map[string]*schema.Schema{ Computed: true, }, "login_url": { - Type: schema.TypeString, - Computed: true, - }, - "config_hash": { - Type: schema.TypeString, - Computed: true, + Description: "Login url using the IDP to Codefresh", + Type: schema.TypeString, + Computed: true, }, "github": { Description: "Settings for GitHub IDP", @@ -71,12 +68,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "authentication_url": { Type: schema.TypeString, Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", @@ -129,12 +120,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "authentication_url": { Type: schema.TypeString, Description: "Authentication url, Defaults to https://gitlab.com", @@ -175,12 +160,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "client_host": { Type: schema.TypeString, Description: "The OKTA organization URL, for example, https://.okta.com", @@ -192,12 +171,6 @@ var idpSchema = map[string]*schema.Schema{ Description: "The Codefresh application ID in your OKTA organization", Optional: true, }, - "app_id_encrypted": { - Type: schema.TypeString, - Description: "Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "sync_mirror_accounts": { Type: schema.TypeList, Description: "The names of the additional Codefresh accounts to be synced from Okta", @@ -211,12 +184,6 @@ var idpSchema = map[string]*schema.Schema{ Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", Optional: true, }, - "access_token_encrypted": { - Type: schema.TypeString, - Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, }, }, }, @@ -239,34 +206,16 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "admin_email": { Type: schema.TypeString, Description: "Email of a user with admin permissions on google, relevant only for synchronization", Optional: true, }, - "admin_email_encrypted": { - Type: schema.TypeString, - Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "json_keyfile": { Type: schema.TypeString, Description: "JSON keyfile for google service account used for synchronization", Optional: true, }, - "json_keyfile_encrypted": { - Type: schema.TypeString, - Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "allowed_groups_for_sync": { Type: schema.TypeString, Description: "Comma separated list of groups to sync", @@ -299,12 +248,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "domain": { Type: schema.TypeString, Description: "The domain of the Auth0 application", @@ -327,12 +270,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "app_id": { Type: schema.TypeString, Description: "The Application ID from your Enterprise Application Properties in Azure AD", @@ -381,12 +318,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "domain": { Type: schema.TypeString, Description: "The domain to be used for authentication", @@ -431,12 +362,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "client_secret_encrypted": { - Type: schema.TypeString, - Description: "Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "host": { Type: schema.TypeString, Description: "The Keycloak URL", @@ -471,12 +396,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "application_certificate_encrypted": { - Type: schema.TypeString, - Description: "Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "provider": { Type: schema.TypeString, Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", @@ -521,34 +440,16 @@ var idpSchema = map[string]*schema.Schema{ Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", Optional: true, }, - "json_keyfile_encrypted": { - Type: schema.TypeString, - Description: "Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "admin_email": { Type: schema.TypeString, Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", Optional: true, }, - "admin_email_encrypted": { - Type: schema.TypeString, - Description: "Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "access_token": { Type: schema.TypeString, Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", Optional: true, }, - "access_token_encrypted": { - Type: schema.TypeString, - Description: "Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, }, }, }, @@ -572,12 +473,6 @@ var idpSchema = map[string]*schema.Schema{ Required: true, Sensitive: true, }, - "password_encrypted": { - Type: schema.TypeString, - Description: "Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "distinguished_name": { Type: schema.TypeString, Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", @@ -599,12 +494,6 @@ var idpSchema = map[string]*schema.Schema{ Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", Optional: true, }, - "certificate_encrypted": { - Type: schema.TypeString, - Description: "Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value", - Optional: true, - Computed: true, - }, "allowed_groups_for_sync": { Type: schema.TypeString, Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", @@ -622,7 +511,7 @@ var idpSchema = map[string]*schema.Schema{ func resourceIdp() *schema.Resource { return &schema.Resource{ - Description: "Identity providers used in Codefresh for user authentication.", + Description: "Codefresh global level identity provider. Requires Codefresh admin token, hence is relevant only for on-prem deployments of Codefresh", Create: resourceIDPCreate, Read: resourceIDPRead, Update: resourceIDPUpdate, @@ -753,13 +642,12 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { // Codefresh API Returns the client secret as an encrypted string on the server side // hence we need to keep in the state the original secret the user provides along with the encrypted computed secret // for Terraform to properly calculate the diff - "client_secret": d.Get("github.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "token_url": cfClientIDP.TokenURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_host": cfClientIDP.ApiHost, - "api_path_prefix": cfClientIDP.ApiPathPrefix, + "client_secret": d.Get("github.0.client_secret"), + "authentication_url": cfClientIDP.AuthURL, + "token_url": cfClientIDP.TokenURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_host": cfClientIDP.ApiHost, + "api_path_prefix": cfClientIDP.ApiPathPrefix, }} d.Set("github", attributes) @@ -767,12 +655,11 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "gitlab" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("gitlab.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "authentication_url": cfClientIDP.AuthURL, - "user_profile_url": cfClientIDP.UserProfileURL, - "api_url": cfClientIDP.ApiURL, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("gitlab.0.client_secret"), + "authentication_url": cfClientIDP.AuthURL, + "user_profile_url": cfClientIDP.UserProfileURL, + "api_url": cfClientIDP.ApiURL, }} d.Set("gitlab", attributes) @@ -780,15 +667,12 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "okta" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("okta.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "client_host": cfClientIDP.ClientHost, - "app_id": d.Get("okta.0.app_id"), - "app_id_encrypted": cfClientIDP.AppId, - "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, - "access_token": d.Get("okta.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("okta.0.client_secret"), + "client_host": cfClientIDP.ClientHost, + "app_id": d.Get("okta.0.app_id"), + "sync_mirror_accounts": cfClientIDP.SyncMirrorAccounts, + "access_token": d.Get("okta.0.access_token"), }} d.Set("okta", attributes) @@ -798,11 +682,8 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, "admin_email": d.Get("google.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, "json_keyfile": d.Get("google.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, "sync_field": cfClientIDP.SyncField, }} @@ -812,10 +693,9 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "auth0" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("auth0.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("auth0.0.client_secret"), + "domain": cfClientIDP.ClientHost, }} d.Set("auth0", attributes) @@ -832,7 +712,6 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { attributes := []map[string]interface{}{{ "app_id": cfClientIDP.ClientId, "client_secret": d.Get("azure.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, "object_id": cfClientIDP.AppId, "autosync_teams_and_users": cfClientIDP.AutoGroupSync, "sync_interval": syncInterval, @@ -844,11 +723,10 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "onelogin" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("onelogin.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "domain": cfClientIDP.ClientHost, - "api_client_id": cfClientIDP.ApiClientId, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("onelogin.0.client_secret"), + "domain": cfClientIDP.ClientHost, + "api_client_id": cfClientIDP.ApiClientId, "api_client_secret": cfClientIDP.ApiClientSecret, "app_id": cfClientIDP.AppId, @@ -859,11 +737,10 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { if cfClientIDP.ClientType == "keycloak" { attributes := []map[string]interface{}{{ - "client_id": cfClientIDP.ClientId, - "client_secret": d.Get("keycloak.0.client_secret"), - "client_secret_encrypted": cfClientIDP.ClientSecret, - "host": cfClientIDP.Host, - "realm": cfClientIDP.Realm, + "client_id": cfClientIDP.ClientId, + "client_secret": d.Get("keycloak.0.client_secret"), + "host": cfClientIDP.Host, + "realm": cfClientIDP.Realm, }} d.Set("keycloak", attributes) @@ -876,22 +753,18 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { return err } attributes := []map[string]interface{}{{ - "endpoint": cfClientIDP.EntryPoint, - "application_certificate": d.Get("saml.0.application_certificate"), - "application_certificate_encrypted": cfClientIDP.ApplicationCert, - "provider": cfClientIDP.SamlProvider, - "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, - "autosync_teams_and_users": cfClientIDP.AutoGroupSync, - "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, - "sync_interval": syncInterval, - "app_id": cfClientIDP.AppId, - "client_host": cfClientIDP.ClientHost, - "json_keyfile": d.Get("saml.0.json_keyfile"), - "json_keyfile_encrypted": cfClientIDP.KeyFile, - "admin_email": d.Get("saml.0.admin_email"), - "admin_email_encrypted": cfClientIDP.Subject, - "access_token": d.Get("saml.0.access_token"), - "access_token_encrypted": cfClientIDP.Access_token, + "endpoint": cfClientIDP.EntryPoint, + "application_certificate": d.Get("saml.0.application_certificate"), + "provider": cfClientIDP.SamlProvider, + "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, + "autosync_teams_and_users": cfClientIDP.AutoGroupSync, + "activate_users_after_sync": cfClientIDP.ActivateUserAfterSync, + "sync_interval": syncInterval, + "app_id": cfClientIDP.AppId, + "client_host": cfClientIDP.ClientHost, + "json_keyfile": d.Get("saml.0.json_keyfile"), + "admin_email": d.Get("saml.0.admin_email"), + "access_token": d.Get("saml.0.access_token"), }} d.Set("saml", attributes) @@ -901,12 +774,10 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { attributes := []map[string]interface{}{{ "url": cfClientIDP.Url, "password": d.Get("ldap.0.password"), - "password_encrypted": cfClientIDP.Password, "distinguished_name": cfClientIDP.DistinguishedName, "search_base": cfClientIDP.SearchBase, "search_filter": cfClientIDP.SearchFilter, "certificate": d.Get("ldap.0.certificate"), - "certificate_encrypted": cfClientIDP.Certificate, "allowed_groups_for_sync": cfClientIDP.AllowedGroupsForSync, "search_base_for_sync": cfClientIDP.SearchBaseForSync, }} diff --git a/docs/data-sources/account_idp.md b/docs/data-sources/account_idp.md new file mode 100644 index 00000000..e88ac829 --- /dev/null +++ b/docs/data-sources/account_idp.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "codefresh_account_idp Data Source - terraform-provider-codefresh" +subcategory: "" +description: |- + This data source retrieves an account level identity provider +--- + +# codefresh_account_idp (Data Source) + +This data source retrieves an account level identity provider + + + + +## Schema + +### Optional + +- `_id` (String) +- `client_name` (String) + +### Read-Only + +- `client_type` (String) +- `display_name` (String) +- `id` (String) The ID of this resource. +- `login_url` (String) Login url using the IDP to Codefresh +- `redirect_ui_url` (String) UI Callback url for the identity provider +- `redirect_url` (String) API Callback url for the identity provider + + diff --git a/docs/resources/account_idp.md b/docs/resources/account_idp.md index 538d98ec..b5f897df 100644 --- a/docs/resources/account_idp.md +++ b/docs/resources/account_idp.md @@ -1,16 +1,49 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "codefresh_account_idp Resource - terraform-provider-codefresh" subcategory: "" description: |- - Identity providers used in Codefresh for user authentication. + Account level identity providers --- # codefresh_account_idp (Resource) -Identity providers used in Codefresh for user authentication. - - +Account level identity providers + +## Example usage +```hcl +resource "codefresh_account_idp" "auth0-test" { + display_name = "tf-auth0-example" + + auth0 { + client_id = "auht0-codefresh-example" + client_secret = "mysecret" + domain = "codefresh.auth0.com" + } +} +``` +```hcl +resource "codefresh_account_idp" "google-example" { + display_name = "tf-google-example" + + google { + client_id = "google-codefresh-example" + client_secret = "mysecret99" + admin_email = "admin@codefresh.io" + sync_field = "myfield" + json_keyfile = < ## Schema @@ -35,10 +68,9 @@ Identity providers used in Codefresh for user authentication. ### Read-Only -- `client_type` (String) Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc) -- `config_hash` (String) +- `client_type` (String) Type of the IDP. Derived from idp specific config object (github, gitlab etc) - `id` (String) The ID of this resource. -- `login_url` (String) +- `login_url` (String) Login url using the IDP to Codefresh - `redirect_ui_url` (String) UI Callback url for the identity provider - `redirect_url` (String) API Callback url for the identity provider @@ -51,10 +83,6 @@ Required: - `client_secret` (String, Sensitive) Client secret from Auth0 - `domain` (String) The domain of the Auth0 application -Optional: - -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - ### Nested Schema for `azure` @@ -67,7 +95,6 @@ Required: Optional: - `autosync_teams_and_users` (Boolean) Set to true to sync user accounts in Azure AD to your Codefresh account -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `object_id` (String) The Object ID from your Enterprise Application Properties in Azure AD - `sync_interval` (Number) Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours - `tenant` (String) Azure tenant @@ -86,7 +113,6 @@ Optional: - `api_host` (String) GitHub API host, Defaults to api.github.com - `api_path_prefix` (String) GitHub API url path prefix, defaults to / - `authentication_url` (String) Authentication url, Defaults to https://github.com/login/oauth/authorize -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `token_url` (String) GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token - `user_profile_url` (String) GitHub user profile url, Defaults to https://api.github.com/user @@ -103,7 +129,6 @@ Optional: - `api_url` (String) Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/ - `authentication_url` (String) Authentication url, Defaults to https://gitlab.com -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `user_profile_url` (String) User profile url, Defaults to https://gitlab.com/api/v4/user @@ -118,11 +143,8 @@ Required: Optional: - `admin_email` (String) Email of a user with admin permissions on google, relevant only for synchronization -- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `allowed_groups_for_sync` (String) Comma separated list of groups to sync -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `json_keyfile` (String) JSON keyfile for google service account used for synchronization -- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `sync_field` (String) Relevant for custom schema-based synchronization only. See Codefresh documentation @@ -137,7 +159,6 @@ Required: Optional: -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `realm` (String) The Realm ID for Codefresh in Keycloak. Defaults to master @@ -154,9 +175,7 @@ Optional: - `allowed_groups_for_sync` (String) To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced - `certificate` (String) For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding -- `certificate_encrypted` (String) Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `distinguished_name` (String) The username to be used to search other users in LDAP notation (combination of cn, ou,dc) -- `password_encrypted` (String) Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `search_base_for_sync` (String) Synchronize using a custom search base, by deafult seach_base is used - `search_filter` (String) The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName @@ -173,10 +192,7 @@ Required: Optional: - `access_token` (String) The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh -- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `app_id` (String) The Codefresh application ID in your OKTA organization -- `app_id_encrypted` (String) Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `sync_mirror_accounts` (List of String) The names of the additional Codefresh accounts to be synced from Okta @@ -194,7 +210,6 @@ Optional: - `api_client_id` (String) Client ID for onelogin API, only needed if syncing users and groups from Onelogin - `api_client_secret` (String) Client secret for onelogin API, only needed if syncing users and groups from Onelogin - `app_id` (String) The Codefresh application ID in your Onelogin -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value @@ -208,18 +223,21 @@ Required: Optional: - `access_token` (String) Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh -- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `activate_users_after_sync` (Boolean) Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false - `admin_email` (String) Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization -- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `allowed_groups_for_sync` (String) Valid for GSuite only: Comma separated list of groups to sync - `app_id` (String) Valid for Okta only: The Codefresh application ID in Okta -- `application_certificate_encrypted` (String) Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `autosync_teams_and_users` (Boolean) Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account - `client_host` (String) Valid for Okta only: OKTA organization URL, for example, https://.okta.com - `json_keyfile` (String) Valid for GSuite only: JSON keyfile for google service account used for synchronization -- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `provider` (String) SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string - `sync_interval` (Number) Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours +## Import + +Please note that secret fields are not imported. +
All secrets should be provided in the configuration and applied after the import for the state to be consistent. +```sh +terraform import codefresh_account_idp.test +``` diff --git a/docs/resources/idp.md b/docs/resources/idp.md index 5af4f672..b05d1e5b 100644 --- a/docs/resources/idp.md +++ b/docs/resources/idp.md @@ -1,16 +1,49 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "codefresh_idp Resource - terraform-provider-codefresh" subcategory: "" description: |- - Identity providers used in Codefresh for user authentication. + Codefresh global level identity provider. Requires Codefresh admin token, hence is relevant only for on-prem deployments of Codefresh --- # codefresh_idp (Resource) -Identity providers used in Codefresh for user authentication. - - +Codefresh global level identity provider. Requires Codefresh admin token, hence is relevant only for on-prem deployments of Codefresh + +## Example usage +```hcl +resource "codefresh_idp" "auth0-test" { + display_name = "tf-auth0-example" + + auth0 { + client_id = "auht0-codefresh-example" + client_secret = "mysecret" + domain = "codefresh.auth0.com" + } +} +``` +```hcl +resource "codefresh_idp" "google-example" { + display_name = "tf-google-example" + + google { + client_id = "google-codefresh-example" + client_secret = "mysecret99" + admin_email = "admin@codefresh.io" + sync_field = "myfield" + json_keyfile = < ## Schema @@ -35,10 +68,9 @@ Identity providers used in Codefresh for user authentication. ### Read-Only -- `client_type` (String) Type of the IDP. If not set it is derived from idp specific config object (github, gitlab etc) -- `config_hash` (String) +- `client_type` (String) Type of the IDP. Derived from idp specific config object (github, gitlab etc) - `id` (String) The ID of this resource. -- `login_url` (String) +- `login_url` (String) Login url using the IDP to Codefresh - `redirect_ui_url` (String) UI Callback url for the identity provider - `redirect_url` (String) API Callback url for the identity provider @@ -51,10 +83,6 @@ Required: - `client_secret` (String, Sensitive) Client secret from Auth0 - `domain` (String) The domain of the Auth0 application -Optional: - -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - ### Nested Schema for `azure` @@ -67,7 +95,6 @@ Required: Optional: - `autosync_teams_and_users` (Boolean) Set to true to sync user accounts in Azure AD to your Codefresh account -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `object_id` (String) The Object ID from your Enterprise Application Properties in Azure AD - `sync_interval` (Number) Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours - `tenant` (String) Azure tenant @@ -86,7 +113,6 @@ Optional: - `api_host` (String) GitHub API host, Defaults to api.github.com - `api_path_prefix` (String) GitHub API url path prefix, defaults to / - `authentication_url` (String) Authentication url, Defaults to https://github.com/login/oauth/authorize -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `token_url` (String) GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token - `user_profile_url` (String) GitHub user profile url, Defaults to https://api.github.com/user @@ -103,7 +129,6 @@ Optional: - `api_url` (String) Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/ - `authentication_url` (String) Authentication url, Defaults to https://gitlab.com -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `user_profile_url` (String) User profile url, Defaults to https://gitlab.com/api/v4/user @@ -118,11 +143,8 @@ Required: Optional: - `admin_email` (String) Email of a user with admin permissions on google, relevant only for synchronization -- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `allowed_groups_for_sync` (String) Comma separated list of groups to sync -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `json_keyfile` (String) JSON keyfile for google service account used for synchronization -- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `sync_field` (String) Relevant for custom schema-based synchronization only. See Codefresh documentation @@ -137,7 +159,6 @@ Required: Optional: -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `realm` (String) The Realm ID for Codefresh in Keycloak. Defaults to master @@ -154,9 +175,7 @@ Optional: - `allowed_groups_for_sync` (String) To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced - `certificate` (String) For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding -- `certificate_encrypted` (String) Computed certificate in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `distinguished_name` (String) The username to be used to search other users in LDAP notation (combination of cn, ou,dc) -- `password_encrypted` (String) Computed password in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `search_base_for_sync` (String) Synchronize using a custom search base, by deafult seach_base is used - `search_filter` (String) The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName @@ -173,10 +192,7 @@ Required: Optional: - `access_token` (String) The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh -- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `app_id` (String) The Codefresh application ID in your OKTA organization -- `app_id_encrypted` (String) Computed app id in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `sync_mirror_accounts` (List of String) The names of the additional Codefresh accounts to be synced from Okta @@ -194,7 +210,6 @@ Optional: - `api_client_id` (String) Client ID for onelogin API, only needed if syncing users and groups from Onelogin - `api_client_secret` (String) Client secret for onelogin API, only needed if syncing users and groups from Onelogin - `app_id` (String) The Codefresh application ID in your Onelogin -- `client_secret_encrypted` (String) Computed client secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value @@ -208,18 +223,21 @@ Required: Optional: - `access_token` (String) Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh -- `access_token_encrypted` (String) Computed access token in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `activate_users_after_sync` (Boolean) Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false - `admin_email` (String) Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization -- `admin_email_encrypted` (String) Admin email in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `allowed_groups_for_sync` (String) Valid for GSuite only: Comma separated list of groups to sync - `app_id` (String) Valid for Okta only: The Codefresh application ID in Okta -- `application_certificate_encrypted` (String) Computed certificate secret in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `autosync_teams_and_users` (Boolean) Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account - `client_host` (String) Valid for Okta only: OKTA organization URL, for example, https://.okta.com - `json_keyfile` (String) Valid for GSuite only: JSON keyfile for google service account used for synchronization -- `json_keyfile_encrypted` (String) Computed JSON keyfile in encrypted form as returned from Codefresh API. Only Codefresh can decrypt this value - `provider` (String) SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string - `sync_interval` (Number) Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours +## Import + +Please note that secret fields are not imported. +
All secrets should be provided in the configuration and applied after the import for the state to be consistent. +```sh +terraform import codefresh_account_idp.test +``` diff --git a/templates/resources/account_idp.md.tmpl b/templates/resources/account_idp.md.tmpl new file mode 100644 index 00000000..9807796a --- /dev/null +++ b/templates/resources/account_idp.md.tmpl @@ -0,0 +1,57 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example usage +```hcl +resource "codefresh_account_idp" "auth0-test" { + display_name = "tf-auth0-example" + + auth0 { + client_id = "auht0-codefresh-example" + client_secret = "mysecret" + domain = "codefresh.auth0.com" + } +} +``` +```hcl +resource "codefresh_account_idp" "google-example" { + display_name = "tf-google-example" + + google { + client_id = "google-codefresh-example" + client_secret = "mysecret99" + admin_email = "admin@codefresh.io" + sync_field = "myfield" + json_keyfile = <All secrets should be provided in the configuration and applied after the import for the state to be consistent. + +```sh +terraform import codefresh_account_idp.test +``` diff --git a/templates/resources/idp.md.tmpl b/templates/resources/idp.md.tmpl new file mode 100644 index 00000000..714f53d1 --- /dev/null +++ b/templates/resources/idp.md.tmpl @@ -0,0 +1,57 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example usage +```hcl +resource "codefresh_idp" "auth0-test" { + display_name = "tf-auth0-example" + + auth0 { + client_id = "auht0-codefresh-example" + client_secret = "mysecret" + domain = "codefresh.auth0.com" + } +} +``` +```hcl +resource "codefresh_idp" "google-example" { + display_name = "tf-google-example" + + google { + client_id = "google-codefresh-example" + client_secret = "mysecret99" + admin_email = "admin@codefresh.io" + sync_field = "myfield" + json_keyfile = <All secrets should be provided in the configuration and applied after the import for the state to be consistent. + +```sh +terraform import codefresh_account_idp.test +``` From fa552a1dc64dc104eab6e49ad4e5d546e98532eb Mon Sep 17 00:00:00 2001 From: Yonatan Koren <10080107+korenyoni@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:17:21 -0500 Subject: [PATCH 24/25] feat: reusable IDP schema and types (#139) ## What * add reusable IDP schema and types ## Why * more DRY ## Notes ## Checklist * [ ] _I have read [CONTRIBUTING.md](https://github.com/codefresh-io/terraform-provider-codefresh/blob/master/CONTRIBUTING.md)._ * [ ] _I have [allowed changes to my fork to be made](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)._ * [ ] _I have added tests, assuming new tests are warranted_. * [ ] _I understand that the `/test` comment will be ignored by the CI trigger [unless it is made by a repo admin or collaborator](https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/#support-for-building-pull-requests-from-forks)._ --- codefresh/internal/idp/doc.go | 2 + codefresh/internal/idp/schema.go | 504 +++++++++++++++++++++++++ codefresh/internal/idp/types.go | 15 + codefresh/resource_account_idp.go | 92 +++-- codefresh/resource_idp.go | 571 ++--------------------------- codefresh/resource_idp_accounts.go | 3 - 6 files changed, 594 insertions(+), 593 deletions(-) create mode 100644 codefresh/internal/idp/doc.go create mode 100644 codefresh/internal/idp/schema.go create mode 100644 codefresh/internal/idp/types.go diff --git a/codefresh/internal/idp/doc.go b/codefresh/internal/idp/doc.go new file mode 100644 index 00000000..fe3c330b --- /dev/null +++ b/codefresh/internal/idp/doc.go @@ -0,0 +1,2 @@ +// Package idp is shared by idp-related resources. +package idp diff --git a/codefresh/internal/idp/schema.go b/codefresh/internal/idp/schema.go new file mode 100644 index 00000000..d068b742 --- /dev/null +++ b/codefresh/internal/idp/schema.go @@ -0,0 +1,504 @@ +package idp + +import ( + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var ( + SupportedIdps = []string{GitHub, GitLab, Okta, Google, Auth0, Auth0, OneLogin, Keycloak, SAML, LDAP} + IdpSchema = map[string]*schema.Schema{ + "display_name": { + Description: "The display name for the IDP.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "Name of the IDP, will be generated if not set", + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "client_type": { + Description: "Type of the IDP. Derived from idp specific config object (github, gitlab etc)", + Type: schema.TypeString, + Computed: true, + ForceNew: true, + }, + "redirect_url": { + Description: "API Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "redirect_ui_url": { + Description: "UI Callback url for the identity provider", + Type: schema.TypeString, + Computed: true, + }, + "login_url": { + Description: "Login url using the IDP to Codefresh", + Type: schema.TypeString, + Computed: true, + }, + "github": { + Description: "Settings for GitHub IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Github", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from GitHub", + Required: true, + Sensitive: true, + }, + "authentication_url": { + Type: schema.TypeString, + Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", + Optional: true, + Default: "https://github.com/login/oauth/authorize", + }, + "token_url": { + Type: schema.TypeString, + Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", + Optional: true, + Default: "https://github.com/login/oauth/access_token", + }, + "user_profile_url": { + Type: schema.TypeString, + Description: "GitHub user profile url, Defaults to https://api.github.com/user", + Optional: true, + Default: "https://api.github.com/user", + }, + "api_host": { + Type: schema.TypeString, + Description: "GitHub API host, Defaults to api.github.com", + Optional: true, + Default: "api.github.com", + }, + "api_path_prefix": { + Type: schema.TypeString, + Description: "GitHub API url path prefix, defaults to /", + Optional: true, + Default: "/", + }, + }, + }, + }, + "gitlab": { + Description: "Settings for GitLab IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Gitlab", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Gitlab", + Required: true, + Sensitive: true, + }, + "authentication_url": { + Type: schema.TypeString, + Description: "Authentication url, Defaults to https://gitlab.com", + Optional: true, + Default: "https://gitlab.com", + }, + "user_profile_url": { + Type: schema.TypeString, + Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", + Optional: true, + Default: "https://gitlab.com/api/v4/user", + }, + "api_url": { + Type: schema.TypeString, + Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", + Optional: true, + Default: "https://gitlab.com/api/v4/", + }, + }, + }, + }, + "okta": { + Description: "Settings for Okta IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Okta", + Required: true, + Sensitive: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "The OKTA organization URL, for example, https://.okta.com", + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your OKTA organization", + Optional: true, + }, + "sync_mirror_accounts": { + Type: schema.TypeList, + Description: "The names of the additional Codefresh accounts to be synced from Okta", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "access_token": { + Type: schema.TypeString, + Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + }, + }, + }, + "google": { + Description: "Settings for Google IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID in Google, must be unique across all identity providers in Codefresh", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret in Google", + Required: true, + Sensitive: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Comma separated list of groups to sync", + Optional: true, + }, + "sync_field": { + Type: schema.TypeString, + Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", + Optional: true, + }, + }, + }, + }, + "auth0": { + Description: "Settings for Auth0 IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Auth0", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Auth0", + Required: true, + Sensitive: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain of the Auth0 application", + Required: true, + }, + }, + }, + }, + "azure": { + Description: "Settings for Azure IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Azure", + Required: true, + Sensitive: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Application ID from your Enterprise Application Properties in Azure AD", + Required: true, + }, + "tenant": { + Type: schema.TypeString, + Description: "Azure tenant", + Optional: true, + }, + "object_id": { + Type: schema.TypeString, + Description: "The Object ID from your Enterprise Application Properties in Azure AD", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + }, + }, + }, + "onelogin": { + Description: "Settings for onelogin IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Onelogin", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Onelogin", + Required: true, + Sensitive: true, + }, + "domain": { + Type: schema.TypeString, + Description: "The domain to be used for authentication", + Required: true, + }, + "app_id": { + Type: schema.TypeString, + Description: "The Codefresh application ID in your Onelogin", + Optional: true, + }, + "api_client_id": { + Type: schema.TypeString, + Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + }, + "api_client_secret": { + Type: schema.TypeString, + Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", + Optional: true, + // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated + // DiffSuppressFunc: surpressObfuscatedFields(), + }, + }, + }, + }, + "keycloak": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "Client ID from Keycloak", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "Client secret from Keycloak", + Required: true, + Sensitive: true, + }, + "host": { + Type: schema.TypeString, + Description: "The Keycloak URL", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), + }, + "realm": { + Type: schema.TypeString, + Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", + Optional: true, + Default: "master", + }, + }, + }, + }, + "saml": { + Description: "Settings for SAML IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint": { + Type: schema.TypeString, + Description: "The SSO endpoint of your Identity Provider", + Required: true, + }, + "application_certificate": { + Type: schema.TypeString, + Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", + Required: true, + Sensitive: true, + }, + "provider": { + Type: schema.TypeString, + Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", + Optional: true, + Default: "", + ValidateFunc: validation.StringInSlice([]string{"", "okta", "GSuite"}, false), + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "Valid for GSuite only: Comma separated list of groups to sync", + Optional: true, + }, + "autosync_teams_and_users": { + Type: schema.TypeBool, + Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", + Optional: true, + Default: false, + }, + "sync_interval": { + Type: schema.TypeInt, + Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", + Optional: true, + }, + "activate_users_after_sync": { + Type: schema.TypeBool, + Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", + Optional: true, + Default: false, + }, + "app_id": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Codefresh application ID in Okta", + Optional: true, + }, + "client_host": { + Type: schema.TypeString, + Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", + Optional: true, + }, + "json_keyfile": { + Type: schema.TypeString, + Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", + Optional: true, + }, + "admin_email": { + Type: schema.TypeString, + Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", + Optional: true, + }, + "access_token": { + Type: schema.TypeString, + Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", + Optional: true, + }, + }, + }, + }, + "ldap": { + Description: "Settings for Keycloak IDP", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: SupportedIdps, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Description: "ldap server url", + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^ldap(s?):\/\/`), "must be a valid ldap url (must start with ldap:// or ldaps://)"), + }, + "password": { + Type: schema.TypeString, + Description: "The password of the user defined in Distinguished name that will be used to search other users", + Required: true, + Sensitive: true, + }, + "distinguished_name": { + Type: schema.TypeString, + Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", + Optional: true, + Computed: true, + }, + "search_base": { + Type: schema.TypeString, + Description: "The search-user scope in LDAP notation", + Required: true, + }, + "search_filter": { + Type: schema.TypeString, + Description: "The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName", + Optional: true, + }, + "certificate": { + Type: schema.TypeString, + Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", + Optional: true, + }, + "allowed_groups_for_sync": { + Type: schema.TypeString, + Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", + Optional: true, + }, + "search_base_for_sync": { + Type: schema.TypeString, + Description: "Synchronize using a custom search base, by deafult seach_base is used", + Optional: true, + }, + }, + }, + }, + } +) diff --git a/codefresh/internal/idp/types.go b/codefresh/internal/idp/types.go new file mode 100644 index 00000000..feaff769 --- /dev/null +++ b/codefresh/internal/idp/types.go @@ -0,0 +1,15 @@ +package idp + +const ( + GitHub string = "github" + GitLab string = "gitlab" + Bitbucket string = "bitbucket" + Okta string = "okta" + Google string = "google" + Auth0 string = "auth0" + Azure string = "azure" + OneLogin string = "onelogin" + Keycloak string = "keycloak" + SAML string = "saml" + LDAP string = "ldap" +) diff --git a/codefresh/resource_account_idp.go b/codefresh/resource_account_idp.go index fc0267c6..025215d0 100644 --- a/codefresh/resource_account_idp.go +++ b/codefresh/resource_account_idp.go @@ -8,6 +8,7 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/idp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -51,16 +52,14 @@ func resourceAccountIdp() *schema.Resource { }), ), // Defined in resource_idp, as schema is the same for global and account scoped IDPs - Schema: idpSchema, + Schema: idp.IdpSchema, } } func resourceAccountIDPCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) id, err := client.CreateIDP(mapResourceToAccountIDP(d), false) - if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) return err @@ -71,7 +70,6 @@ func resourceAccountIDPCreate(d *schema.ResourceData, meta interface{}) error { } func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) idpID := d.Id() @@ -79,7 +77,6 @@ func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { var err error cfClientIDP, err = client.GetAccountIdpByID(idpID) - if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { d.SetId("") @@ -91,7 +88,6 @@ func resourceAccountIDPRead(d *schema.ResourceData, meta interface{}) error { } err = mapAccountIDPToResource(*cfClientIDP, d) - if err != nil { log.Printf("[DEBUG] Error while getting mapping response to IDP object. Error = %v", err) return err @@ -104,7 +100,6 @@ func resourceAccountIDPDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*cfclient.Client) err := client.DeleteIDPAccount(d.Id()) - if err != nil { log.Printf("[DEBUG] Error while deleting account level IDP. Error = %v", err) return err @@ -114,11 +109,9 @@ func resourceAccountIDPDelete(d *schema.ResourceData, meta interface{}) error { } func resourceAccountIDPUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) err := client.UpdateIDP(mapResourceToAccountIDP(d), false) - if err != nil { log.Printf("[DEBUG] Error while updating idp. Error = %v", err) return err @@ -136,7 +129,7 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e d.Set("login_url", cfClientIDP.LoginUrl) d.Set("client_type", cfClientIDP.ClientType) - if cfClientIDP.ClientType == "github" { + if cfClientIDP.ClientType == idp.GitHub { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, // Codefresh API Returns the client secret as an encrypted string on the server side @@ -150,10 +143,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "api_path_prefix": cfClientIDP.ApiPathPrefix, }} - d.Set("github", attributes) + d.Set(idp.GitHub, attributes) } - if cfClientIDP.ClientType == "gitlab" { + if cfClientIDP.ClientType == idp.GitLab { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("gitlab.0.client_secret"), @@ -162,10 +155,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "api_url": cfClientIDP.ApiURL, }} - d.Set("gitlab", attributes) + d.Set(idp.GitLab, attributes) } - if cfClientIDP.ClientType == "okta" { + if cfClientIDP.ClientType == idp.Okta { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("okta.0.client_secret"), @@ -178,7 +171,7 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e d.Set("okta", attributes) } - if cfClientIDP.ClientType == "google" { + if cfClientIDP.ClientType == idp.Google { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), @@ -188,23 +181,22 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "sync_field": cfClientIDP.SyncField, }} - d.Set("google", attributes) + d.Set(idp.Google, attributes) } - if cfClientIDP.ClientType == "auth0" { + if cfClientIDP.ClientType == idp.Auth0 { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("auth0.0.client_secret"), "domain": cfClientIDP.ClientHost, }} - d.Set("auth0", attributes) + d.Set(idp.Auth0, attributes) } - if cfClientIDP.ClientType == "azure" { + if cfClientIDP.ClientType == idp.Azure { syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) - if err != nil { return err } @@ -218,10 +210,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "tenant": cfClientIDP.Tenant, }} - d.Set("azure", attributes) + d.Set(idp.Azure, attributes) } - if cfClientIDP.ClientType == "onelogin" { + if cfClientIDP.ClientType == idp.OneLogin { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("onelogin.0.client_secret"), @@ -234,10 +226,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "app_id": cfClientIDP.AppId, }} - d.Set("onelogin", attributes) + d.Set(idp.OneLogin, attributes) } - if cfClientIDP.ClientType == "keycloak" { + if cfClientIDP.ClientType == idp.Keycloak { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("keycloak.0.client_secret"), @@ -245,12 +237,11 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "realm": cfClientIDP.Realm, }} - d.Set("keycloak", attributes) + d.Set(idp.Keycloak, attributes) } - if cfClientIDP.ClientType == "saml" { + if cfClientIDP.ClientType == idp.SAML { syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) - if err != nil { return err } @@ -269,10 +260,10 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "access_token": d.Get("saml.0.access_token"), }} - d.Set("saml", attributes) + d.Set(idp.SAML, attributes) } - if cfClientIDP.ClientType == "ldap" { + if cfClientIDP.ClientType == idp.LDAP { attributes := []map[string]interface{}{{ "url": cfClientIDP.Url, "password": d.Get("ldap.0.password"), @@ -284,14 +275,13 @@ func mapAccountIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) e "search_base_for_sync": cfClientIDP.SearchBaseForSync, }} - d.Set("ldap", attributes) + d.Set(idp.LDAP, attributes) } return nil } func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { - cfClientIDP := &cfclient.IDP{ ID: d.Id(), DisplayName: d.Get("display_name").(string), @@ -301,8 +291,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { LoginUrl: d.Get("login_url").(string), } - if _, ok := d.GetOk("github"); ok { - cfClientIDP.ClientType = "github" + if _, ok := d.GetOk(idp.GitHub); ok { + cfClientIDP.ClientType = idp.GitHub cfClientIDP.ClientId = d.Get("github.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) @@ -312,8 +302,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) } - if _, ok := d.GetOk("gitlab"); ok { - cfClientIDP.ClientType = "gitlab" + if _, ok := d.GetOk(idp.GitLab); ok { + cfClientIDP.ClientType = idp.GitLab cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("gitlab.0.authentication_url").(string) @@ -321,8 +311,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) } - if _, ok := d.GetOk("okta"); ok { - cfClientIDP.ClientType = "okta" + if _, ok := d.GetOk(idp.Okta); ok { + cfClientIDP.ClientType = idp.Okta cfClientIDP.ClientId = d.Get("okta.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("okta.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("okta.0.client_host").(string) @@ -331,8 +321,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Access_token = d.Get("okta.0.access_token").(string) } - if _, ok := d.GetOk("google"); ok { - cfClientIDP.ClientType = "google" + if _, ok := d.GetOk(idp.Google); ok { + cfClientIDP.ClientType = idp.Google cfClientIDP.ClientId = d.Get("google.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("google.0.client_secret").(string) cfClientIDP.KeyFile = d.Get("google.0.json_keyfile").(string) @@ -341,15 +331,15 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.SyncField = d.Get("google.0.sync_field").(string) } - if _, ok := d.GetOk("auth0"); ok { - cfClientIDP.ClientType = "auth0" + if _, ok := d.GetOk(idp.Auth0); ok { + cfClientIDP.ClientType = idp.Auth0 cfClientIDP.ClientId = d.Get("auth0.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("auth0.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("auth0.0.domain").(string) } - if _, ok := d.GetOk("azure"); ok { - cfClientIDP.ClientType = "azure" + if _, ok := d.GetOk(idp.Azure); ok { + cfClientIDP.ClientType = idp.Azure cfClientIDP.ClientId = d.Get("azure.0.app_id").(string) cfClientIDP.ClientSecret = d.Get("azure.0.client_secret").(string) cfClientIDP.AppId = d.Get("azure.0.object_id").(string) @@ -358,8 +348,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.SyncInterval = strconv.Itoa(d.Get("azure.0.sync_interval").(int)) } - if _, ok := d.GetOk("onelogin"); ok { - cfClientIDP.ClientType = "onelogin" + if _, ok := d.GetOk(idp.OneLogin); ok { + cfClientIDP.ClientType = idp.OneLogin cfClientIDP.ClientId = d.Get("onelogin.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("onelogin.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("onelogin.0.domain").(string) @@ -368,16 +358,16 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiClientSecret = d.Get("onelogin.0.api_client_secret").(string) } - if _, ok := d.GetOk("keycloak"); ok { - cfClientIDP.ClientType = "keycloak" + if _, ok := d.GetOk(idp.Keycloak); ok { + cfClientIDP.ClientType = idp.Keycloak cfClientIDP.ClientId = d.Get("keycloak.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("keycloak.0.client_secret").(string) cfClientIDP.Host = d.Get("keycloak.0.host").(string) cfClientIDP.Realm = d.Get("keycloak.0.realm").(string) } - if _, ok := d.GetOk("saml"); ok { - cfClientIDP.ClientType = "saml" + if _, ok := d.GetOk(idp.SAML); ok { + cfClientIDP.ClientType = idp.SAML cfClientIDP.SamlProvider = d.Get("saml.0.provider").(string) cfClientIDP.EntryPoint = d.Get("saml.0.endpoint").(string) cfClientIDP.ApplicationCert = d.Get("saml.0.application_certificate").(string) @@ -392,8 +382,8 @@ func mapResourceToAccountIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Access_token = d.Get("saml.0.access_token").(string) } - if _, ok := d.GetOk("ldap"); ok { - cfClientIDP.ClientType = "ldap" + if _, ok := d.GetOk(idp.LDAP); ok { + cfClientIDP.ClientType = idp.LDAP cfClientIDP.Url = d.Get("ldap.0.url").(string) cfClientIDP.Password = d.Get("ldap.0.password").(string) cfClientIDP.DistinguishedName = d.Get("ldap.0.distinguished_name").(string) diff --git a/codefresh/resource_idp.go b/codefresh/resource_idp.go index 994cac9c..49af78a9 100644 --- a/codefresh/resource_idp.go +++ b/codefresh/resource_idp.go @@ -5,510 +5,15 @@ import ( "errors" "fmt" "log" - "regexp" "strconv" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/datautil" + "github.com/codefresh-io/terraform-provider-codefresh/codefresh/internal/idp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -var supportedIdps = []string{"github", "gitlab", "okta", "google", "auth0", "azure", "onelogin", "keycloak", "saml", "ldap"} -var idpSchema = map[string]*schema.Schema{ - "display_name": { - Description: "The display name for the IDP.", - Type: schema.TypeString, - Required: true, - }, - "name": { - Description: "Name of the IDP, will be generated if not set", - Type: schema.TypeString, - Computed: true, - Optional: true, - }, - "client_type": { - Description: "Type of the IDP. Derived from idp specific config object (github, gitlab etc)", - Type: schema.TypeString, - Computed: true, - ForceNew: true, - }, - "redirect_url": { - Description: "API Callback url for the identity provider", - Type: schema.TypeString, - Computed: true, - }, - "redirect_ui_url": { - Description: "UI Callback url for the identity provider", - Type: schema.TypeString, - Computed: true, - }, - "login_url": { - Description: "Login url using the IDP to Codefresh", - Type: schema.TypeString, - Computed: true, - }, - "github": { - Description: "Settings for GitHub IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Github", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from GitHub", - Required: true, - Sensitive: true, - }, - "authentication_url": { - Type: schema.TypeString, - Description: "Authentication url, Defaults to https://github.com/login/oauth/authorize", - Optional: true, - Default: "https://github.com/login/oauth/authorize", - }, - "token_url": { - Type: schema.TypeString, - Description: "GitHub token endpoint url, Defaults to https://github.com/login/oauth/access_token", - Optional: true, - Default: "https://github.com/login/oauth/access_token", - }, - "user_profile_url": { - Type: schema.TypeString, - Description: "GitHub user profile url, Defaults to https://api.github.com/user", - Optional: true, - Default: "https://api.github.com/user", - }, - "api_host": { - Type: schema.TypeString, - Description: "GitHub API host, Defaults to api.github.com", - Optional: true, - Default: "api.github.com", - }, - "api_path_prefix": { - Type: schema.TypeString, - Description: "GitHub API url path prefix, defaults to /", - Optional: true, - Default: "/", - }, - }, - }, - }, - "gitlab": { - Description: "Settings for GitLab IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Gitlab", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Gitlab", - Required: true, - Sensitive: true, - }, - "authentication_url": { - Type: schema.TypeString, - Description: "Authentication url, Defaults to https://gitlab.com", - Optional: true, - Default: "https://gitlab.com", - }, - "user_profile_url": { - Type: schema.TypeString, - Description: "User profile url, Defaults to https://gitlab.com/api/v4/user", - Optional: true, - Default: "https://gitlab.com/api/v4/user", - }, - "api_url": { - Type: schema.TypeString, - Description: "Base url for Gitlab API, Defaults to https://gitlab.com/api/v4/", - Optional: true, - Default: "https://gitlab.com/api/v4/", - }, - }, - }, - }, - "okta": { - Description: "Settings for Okta IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID in Okta, must be unique across all identity providers in Codefresh", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret in Okta", - Required: true, - Sensitive: true, - }, - "client_host": { - Type: schema.TypeString, - Description: "The OKTA organization URL, for example, https://.okta.com", - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)(\.okta(preview|-emea)?\.com$)`), "must be a valid okta url"), - Required: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Codefresh application ID in your OKTA organization", - Optional: true, - }, - "sync_mirror_accounts": { - Type: schema.TypeList, - Description: "The names of the additional Codefresh accounts to be synced from Okta", - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "access_token": { - Type: schema.TypeString, - Description: "The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, - }, - }, - }, - }, - "google": { - Description: "Settings for Google IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID in Google, must be unique across all identity providers in Codefresh", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret in Google", - Required: true, - Sensitive: true, - }, - "admin_email": { - Type: schema.TypeString, - Description: "Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, - }, - "json_keyfile": { - Type: schema.TypeString, - Description: "JSON keyfile for google service account used for synchronization", - Optional: true, - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "Comma separated list of groups to sync", - Optional: true, - }, - "sync_field": { - Type: schema.TypeString, - Description: "Relevant for custom schema-based synchronization only. See Codefresh documentation", - Optional: true, - }, - }, - }, - }, - "auth0": { - Description: "Settings for Auth0 IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Auth0", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Auth0", - Required: true, - Sensitive: true, - }, - "domain": { - Type: schema.TypeString, - Description: "The domain of the Auth0 application", - Required: true, - }, - }, - }, - }, - "azure": { - Description: "Settings for Azure IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Azure", - Required: true, - Sensitive: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Application ID from your Enterprise Application Properties in Azure AD", - Required: true, - }, - "tenant": { - Type: schema.TypeString, - Description: "Azure tenant", - Optional: true, - }, - "object_id": { - Type: schema.TypeString, - Description: "The Object ID from your Enterprise Application Properties in Azure AD", - Optional: true, - }, - "autosync_teams_and_users": { - Type: schema.TypeBool, - Description: "Set to true to sync user accounts in Azure AD to your Codefresh account", - Optional: true, - Default: false, - }, - "sync_interval": { - Type: schema.TypeInt, - Description: "Sync interval in hours for syncing user accounts in Azure AD to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, - }, - }, - }, - }, - "onelogin": { - Description: "Settings for onelogin IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Onelogin", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Onelogin", - Required: true, - Sensitive: true, - }, - "domain": { - Type: schema.TypeString, - Description: "The domain to be used for authentication", - Required: true, - }, - "app_id": { - Type: schema.TypeString, - Description: "The Codefresh application ID in your Onelogin", - Optional: true, - }, - "api_client_id": { - Type: schema.TypeString, - Description: "Client ID for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, - }, - "api_client_secret": { - Type: schema.TypeString, - Description: "Client secret for onelogin API, only needed if syncing users and groups from Onelogin", - Optional: true, - // When onelogin IDP is created on account level, after the first apply the client secret is returned obfuscated - //DiffSuppressFunc: surpressObfuscatedFields(), - }, - }, - }, - }, - "keycloak": { - Description: "Settings for Keycloak IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "Client ID from Keycloak", - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "Client secret from Keycloak", - Required: true, - Sensitive: true, - }, - "host": { - Type: schema.TypeString, - Description: "The Keycloak URL", - Required: true, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^(https?:\/\/)(\S+)$`), "must be a valid url"), - }, - "realm": { - Type: schema.TypeString, - Description: "The Realm ID for Codefresh in Keycloak. Defaults to master", - Optional: true, - Default: "master", - }, - }, - }, - }, - "saml": { - Description: "Settings for SAML IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "endpoint": { - Type: schema.TypeString, - Description: "The SSO endpoint of your Identity Provider", - Required: true, - }, - "application_certificate": { - Type: schema.TypeString, - Description: "The security certificate of your Identity Provider. Paste the value directly on the field. Do not convert to base64 or any other encoding by hand", - Required: true, - Sensitive: true, - }, - "provider": { - Type: schema.TypeString, - Description: "SAML provider. Currently supported values - GSuite, okta or empty string for generic provider. Defaults to empty string", - Optional: true, - Default: "", - ValidateFunc: validation.StringInSlice([]string{"", "okta", "GSuite"}, false), - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "Valid for GSuite only: Comma separated list of groups to sync", - Optional: true, - }, - "autosync_teams_and_users": { - Type: schema.TypeBool, - Description: "Valid for Okta/GSuite: Set to true to sync user accounts and teams in okta/gsuite to your Codefresh account", - Optional: true, - Default: false, - }, - "sync_interval": { - Type: schema.TypeInt, - Description: "Valid for Okta/GSuite: Sync interval in hours for syncing user accounts in okta/gsuite to your Codefresh account. If not set the sync inteval will be 12 hours", - Optional: true, - }, - "activate_users_after_sync": { - Type: schema.TypeBool, - Description: "Valid for Okta only: If set to true, Codefresh will automatically invite and activate new users added during the automated sync, without waiting for the users to accept the invitations. Defaults to false", - Optional: true, - Default: false, - }, - "app_id": { - Type: schema.TypeString, - Description: "Valid for Okta only: The Codefresh application ID in Okta", - Optional: true, - }, - "client_host": { - Type: schema.TypeString, - Description: "Valid for Okta only: OKTA organization URL, for example, https://.okta.com", - Optional: true, - }, - "json_keyfile": { - Type: schema.TypeString, - Description: "Valid for GSuite only: JSON keyfile for google service account used for synchronization", - Optional: true, - }, - "admin_email": { - Type: schema.TypeString, - Description: "Valid for GSuite only: Email of a user with admin permissions on google, relevant only for synchronization", - Optional: true, - }, - "access_token": { - Type: schema.TypeString, - Description: "Valid for Okta only: The Okta API token generated in Okta, used to sync groups and their users from Okta to Codefresh", - Optional: true, - }, - }, - }, - }, - "ldap": { - Description: "Settings for Keycloak IDP", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ExactlyOneOf: supportedIdps, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Description: "ldap server url", - Required: true, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^ldap(s?):\/\/`), "must be a valid ldap url (must start with ldap:// or ldaps://)"), - }, - "password": { - Type: schema.TypeString, - Description: "The password of the user defined in Distinguished name that will be used to search other users", - Required: true, - Sensitive: true, - }, - "distinguished_name": { - Type: schema.TypeString, - Description: "The username to be used to search other users in LDAP notation (combination of cn, ou,dc)", - Optional: true, - Computed: true, - }, - "search_base": { - Type: schema.TypeString, - Description: "The search-user scope in LDAP notation", - Required: true, - }, - "search_filter": { - Type: schema.TypeString, - Description: "The attribute by which to search for the user on the LDAP server. By default, set to uid. For the Azure LDAP server, set this field to sAMAccountName", - Optional: true, - }, - "certificate": { - Type: schema.TypeString, - Description: "For ldaps only: The security certificate of the LDAP server. Do not convert to base64 or any other encoding", - Optional: true, - }, - "allowed_groups_for_sync": { - Type: schema.TypeString, - Description: "To sync only by specified groups - specify a comma separated list of groups, by default all groups will be synced", - Optional: true, - }, - "search_base_for_sync": { - Type: schema.TypeString, - Description: "Synchronize using a custom search base, by deafult seach_base is used", - Optional: true, - }, - }, - }, - }, -} - func resourceIdp() *schema.Resource { return &schema.Resource{ Description: "Codefresh global level identity provider. Requires Codefresh admin token, hence is relevant only for on-prem deployments of Codefresh", @@ -536,16 +41,14 @@ func resourceIdp() *schema.Resource { } }), ), - Schema: idpSchema, + Schema: idp.IdpSchema, } } func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) id, err := client.CreateIDP(mapResourceToIDP(d), true) - if err != nil { log.Printf("[DEBUG] Error while creating idp. Error = %v", err) return err @@ -556,7 +59,6 @@ func resourceIDPCreate(d *schema.ResourceData, meta interface{}) error { } func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) idpID := d.Id() @@ -564,7 +66,6 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { var err error cfClientIDP, err = client.GetIdpByID(idpID) - if err != nil { if err.Error() == fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", d.Id()) { d.SetId("") @@ -576,7 +77,6 @@ func resourceIDPRead(d *schema.ResourceData, meta interface{}) error { } err = mapIDPToResource(*cfClientIDP, d) - if err != nil { log.Printf("[DEBUG] Error while getting mapping response to IDP object. Error = %v", err) return err @@ -593,7 +93,6 @@ func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { var err error cfClientIDP, err = client.GetIdpByID(idpID) - if err != nil { log.Printf("[DEBUG] Error while getting IDP. Error = %v", err) return err @@ -604,7 +103,6 @@ func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { } err = client.DeleteIDP(d.Id()) - if err != nil { log.Printf("[DEBUG] Error while deleting IDP. Error = %v", err) return err @@ -614,11 +112,9 @@ func resourceIDPDelete(d *schema.ResourceData, meta interface{}) error { } func resourceIDPUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) err := client.UpdateIDP(mapResourceToIDP(d), true) - if err != nil { log.Printf("[DEBUG] Error while updating idp. Error = %v", err) return err @@ -636,7 +132,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("login_url", cfClientIDP.LoginUrl) d.Set("client_type", cfClientIDP.ClientType) - if cfClientIDP.ClientType == "github" { + if cfClientIDP.ClientType == idp.GitHub { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, // Codefresh API Returns the client secret as an encrypted string on the server side @@ -653,7 +149,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("github", attributes) } - if cfClientIDP.ClientType == "gitlab" { + if cfClientIDP.ClientType == idp.GitLab { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("gitlab.0.client_secret"), @@ -665,7 +161,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("gitlab", attributes) } - if cfClientIDP.ClientType == "okta" { + if cfClientIDP.ClientType == idp.Okta { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("okta.0.client_secret"), @@ -678,7 +174,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("okta", attributes) } - if cfClientIDP.ClientType == "google" { + if cfClientIDP.ClientType == idp.Google { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("google.0.client_secret"), @@ -691,7 +187,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("google", attributes) } - if cfClientIDP.ClientType == "auth0" { + if cfClientIDP.ClientType == idp.Auth0 { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("auth0.0.client_secret"), @@ -701,10 +197,9 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("auth0", attributes) } - if cfClientIDP.ClientType == "azure" { + if cfClientIDP.ClientType == idp.Azure { syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) - if err != nil { return err } @@ -721,7 +216,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("azure", attributes) } - if cfClientIDP.ClientType == "onelogin" { + if cfClientIDP.ClientType == idp.OneLogin { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("onelogin.0.client_secret"), @@ -735,7 +230,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("onelogin", attributes) } - if cfClientIDP.ClientType == "keycloak" { + if cfClientIDP.ClientType == idp.Keycloak { attributes := []map[string]interface{}{{ "client_id": cfClientIDP.ClientId, "client_secret": d.Get("keycloak.0.client_secret"), @@ -746,9 +241,8 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("keycloak", attributes) } - if cfClientIDP.ClientType == "saml" { + if cfClientIDP.ClientType == idp.SAML { syncInterval, err := strconv.Atoi(cfClientIDP.SyncInterval) - if err != nil { return err } @@ -770,7 +264,7 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { d.Set("saml", attributes) } - if cfClientIDP.ClientType == "ldap" { + if cfClientIDP.ClientType == idp.LDAP { attributes := []map[string]interface{}{{ "url": cfClientIDP.Url, "password": d.Get("ldap.0.password"), @@ -789,7 +283,6 @@ func mapIDPToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error { } func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { - cfClientIDP := &cfclient.IDP{ ID: d.Id(), DisplayName: d.Get("display_name").(string), @@ -799,8 +292,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { LoginUrl: d.Get("login_url").(string), } - if _, ok := d.GetOk("github"); ok { - cfClientIDP.ClientType = "github" + if _, ok := d.GetOk(idp.GitHub); ok { + cfClientIDP.ClientType = idp.GitHub cfClientIDP.ClientId = d.Get("github.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("github.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("github.0.authentication_url").(string) @@ -810,8 +303,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiPathPrefix = d.Get("github.0.api_path_prefix").(string) } - if _, ok := d.GetOk("gitlab"); ok { - cfClientIDP.ClientType = "gitlab" + if _, ok := d.GetOk(idp.GitLab); ok { + cfClientIDP.ClientType = idp.GitLab cfClientIDP.ClientId = d.Get("gitlab.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("gitlab.0.client_secret").(string) cfClientIDP.AuthURL = d.Get("gitlab.0.authentication_url").(string) @@ -819,8 +312,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiURL = d.Get("gitlab.0.api_url").(string) } - if _, ok := d.GetOk("okta"); ok { - cfClientIDP.ClientType = "okta" + if _, ok := d.GetOk(idp.Okta); ok { + cfClientIDP.ClientType = idp.Okta cfClientIDP.ClientId = d.Get("okta.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("okta.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("okta.0.client_host").(string) @@ -829,8 +322,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Access_token = d.Get("okta.0.access_token").(string) } - if _, ok := d.GetOk("google"); ok { - cfClientIDP.ClientType = "google" + if _, ok := d.GetOk(idp.Google); ok { + cfClientIDP.ClientType = idp.Google cfClientIDP.ClientId = d.Get("google.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("google.0.client_secret").(string) cfClientIDP.KeyFile = d.Get("google.0.json_keyfile").(string) @@ -839,15 +332,15 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.SyncField = d.Get("google.0.sync_field").(string) } - if _, ok := d.GetOk("auth0"); ok { - cfClientIDP.ClientType = "auth0" + if _, ok := d.GetOk(idp.Auth0); ok { + cfClientIDP.ClientType = idp.Auth0 cfClientIDP.ClientId = d.Get("auth0.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("auth0.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("auth0.0.domain").(string) } - if _, ok := d.GetOk("azure"); ok { - cfClientIDP.ClientType = "azure" + if _, ok := d.GetOk(idp.Azure); ok { + cfClientIDP.ClientType = idp.Azure cfClientIDP.ClientId = d.Get("azure.0.app_id").(string) cfClientIDP.ClientSecret = d.Get("azure.0.client_secret").(string) cfClientIDP.AppId = d.Get("azure.0.object_id").(string) @@ -856,8 +349,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.SyncInterval = strconv.Itoa(d.Get("azure.0.sync_interval").(int)) } - if _, ok := d.GetOk("onelogin"); ok { - cfClientIDP.ClientType = "onelogin" + if _, ok := d.GetOk(idp.OneLogin); ok { + cfClientIDP.ClientType = idp.OneLogin cfClientIDP.ClientId = d.Get("onelogin.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("onelogin.0.client_secret").(string) cfClientIDP.ClientHost = d.Get("onelogin.0.domain").(string) @@ -866,16 +359,16 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.ApiClientSecret = d.Get("onelogin.0.api_client_secret").(string) } - if _, ok := d.GetOk("keycloak"); ok { - cfClientIDP.ClientType = "keycloak" + if _, ok := d.GetOk(idp.Keycloak); ok { + cfClientIDP.ClientType = idp.Keycloak cfClientIDP.ClientId = d.Get("keycloak.0.client_id").(string) cfClientIDP.ClientSecret = d.Get("keycloak.0.client_secret").(string) cfClientIDP.Host = d.Get("keycloak.0.host").(string) cfClientIDP.Realm = d.Get("keycloak.0.realm").(string) } - if _, ok := d.GetOk("saml"); ok { - cfClientIDP.ClientType = "saml" + if _, ok := d.GetOk(idp.SAML); ok { + cfClientIDP.ClientType = idp.SAML cfClientIDP.SamlProvider = d.Get("saml.0.provider").(string) cfClientIDP.EntryPoint = d.Get("saml.0.endpoint").(string) cfClientIDP.ApplicationCert = d.Get("saml.0.application_certificate").(string) @@ -890,8 +383,8 @@ func mapResourceToIDP(d *schema.ResourceData) *cfclient.IDP { cfClientIDP.Access_token = d.Get("saml.0.access_token").(string) } - if _, ok := d.GetOk("ldap"); ok { - cfClientIDP.ClientType = "ldap" + if _, ok := d.GetOk(idp.LDAP); ok { + cfClientIDP.ClientType = idp.LDAP cfClientIDP.Url = d.Get("ldap.0.url").(string) cfClientIDP.Password = d.Get("ldap.0.password").(string) cfClientIDP.DistinguishedName = d.Get("ldap.0.distinguished_name").(string) diff --git a/codefresh/resource_idp_accounts.go b/codefresh/resource_idp_accounts.go index 4509c1ee..dc625d17 100644 --- a/codefresh/resource_idp_accounts.go +++ b/codefresh/resource_idp_accounts.go @@ -38,7 +38,6 @@ Because of the current Codefresh API limitation it's impossible to remove accoun } func resourceIDPAccountsCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) accountIds := datautil.ConvertStringArr(d.Get("account_ids").(*schema.Set).List()) @@ -60,7 +59,6 @@ func resourceIDPAccountsCreate(d *schema.ResourceData, meta interface{}) error { } func resourceIDPAccountsRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) idpID := d.Id() @@ -94,7 +92,6 @@ func resourceIDPAccountsDelete(_ *schema.ResourceData, _ interface{}) error { } func resourceIDPAccountsUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cfclient.Client) idpID := d.Id() From beb33c52582d2d3776807f59a8566ef51c4781b3 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 28 Feb 2024 08:36:18 +0200 Subject: [PATCH 25/25] fix supported idp types array --- codefresh/internal/idp/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codefresh/internal/idp/schema.go b/codefresh/internal/idp/schema.go index d068b742..5851b984 100644 --- a/codefresh/internal/idp/schema.go +++ b/codefresh/internal/idp/schema.go @@ -8,7 +8,7 @@ import ( ) var ( - SupportedIdps = []string{GitHub, GitLab, Okta, Google, Auth0, Auth0, OneLogin, Keycloak, SAML, LDAP} + SupportedIdps = []string{GitHub, GitLab, Okta, Google, Auth0, Azure, OneLogin, Keycloak, SAML, LDAP} IdpSchema = map[string]*schema.Schema{ "display_name": { Description: "The display name for the IDP.",