Skip to content

Commit

Permalink
feature/add-oidc-parameters (#351)
Browse files Browse the repository at this point in the history
* k8s resource: add OIDC parameters

* update gridscale_k8s release version to 1.29 in acc test

* remove oidc list block

because oicd params are for the whole cluster

* update k8s acc test

* fix backcompatibility issue

setting to computed allows the backend to update the state without triggering a prompt for update

---------

Co-authored-by: Van Thong Nguyen <[email protected]>
  • Loading branch information
alidaw and nvthongswansea authored Jul 11, 2024
1 parent 719da5f commit d68bba5
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 2 deletions.
247 changes: 247 additions & 0 deletions gridscale/resource_gridscale_k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package gridscale

import (
"context"
"encoding/pem"
"errors"
"fmt"
"net"
"net/http"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -239,6 +241,66 @@ func resourceGridscaleK8s() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"oidc_enabled": {
Type: schema.TypeBool,
Description: "Disable or enable OIDC",
Computed: true,
Optional: true,
},
"oidc_issuer_url": {
Type: schema.TypeString,
Description: "URL of the provider that allows the API server to discover public signing keys. Only URLs that use the https:// scheme are accepted.",
Computed: true,
Optional: true,
},
"oidc_client_id": {
Type: schema.TypeString,
Description: "A client ID that all tokens must be issued for.",
Computed: true,
Optional: true,
},
"oidc_username_claim": {
Type: schema.TypeString,
Description: "JWT claim to use as the user name.",
Computed: true,
Optional: true,
},
"oidc_groups_claim": {
Type: schema.TypeString,
Description: "JWT claim to use as the user's group.",
Computed: true,
Optional: true,
},
"oidc_signing_algs": {
Type: schema.TypeString,
Description: "The signing algorithms accepted. Default is 'RS256'. Other option is 'RS512'.",
Computed: true,
Optional: true,
},
"oidc_groups_prefix": {
Type: schema.TypeString,
Description: "Prefix prepended to group claims to prevent clashes with existing names (such as system: groups). For example, the value oidc: will create group names like oidc:engineering and oidc:infra.",
Computed: true,
Optional: true,
},
"oidc_username_prefix": {
Type: schema.TypeString,
Description: "Prefix prepended to username claims to prevent clashes with existing names (such as system: users). For example, the value oidc: will create usernames like oidc:jane.doe. If this flag isn't provided and --oidc-username-claim is a value other than email the prefix defaults to ( Issuer URL )# where ( Issuer URL ) is the value of --oidc-issuer-url. The value - can be used to disable all prefixing.",
Computed: true,
Optional: true,
},
"oidc_required_claim": {
Type: schema.TypeString,
Description: "A key=value pair that describes a required claim in the ID Token. Multiple claims can be set like this: key1=value1,key2=value2",
Computed: true,
Optional: true,
},
"oidc_ca_pem": {
Type: schema.TypeString,
Description: "Custom CA from customer in pem format as string.",
Computed: true,
Optional: true,
},
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(45 * time.Minute),
Expand Down Expand Up @@ -304,6 +366,76 @@ func resourceGridscaleK8sRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("%s error setting service_template_uuid: %v", errorPrefix, err)
}

// Set flag telling if enabled or not
if enabled, ok := props.Parameters["k8s_oidc_enabled"].(bool); ok {
if err = d.Set("oidc_enabled", enabled); err != nil {
return fmt.Errorf("%s error setting oidc_enabled: %v", errorPrefix, err)
}
}

// Set issuer URL if it is set
if issuerURL, isIssuerURLSet := props.Parameters["k8s_oidc_issuer_url"]; isIssuerURLSet {
if err = d.Set("oidc_issuer_url", issuerURL); err != nil {
return fmt.Errorf("%s error setting oidc_issuer_url: %v", errorPrefix, err)
}
}

// Set client ID if it is set
if clientID, isClientIDSet := props.Parameters["k8s_oidc_client_id"]; isClientIDSet {
if err = d.Set("oidc_client_id", clientID); err != nil {
return fmt.Errorf("%s error setting oidc_client_id: %v", errorPrefix, err)
}
}

// Set username claim if it is set
if usernameClaimSet, isUsernameClaimSet := props.Parameters["k8s_oidc_username_claim"]; isUsernameClaimSet {
if err = d.Set("oidc_username_claim", usernameClaimSet); err != nil {
return fmt.Errorf("%s error setting oidc_username_claim: %v", errorPrefix, err)
}
}

// Set groups claim if it is set
if groupsClain, isGroupsClaimSet := props.Parameters["k8s_oidc_groups_claim"]; isGroupsClaimSet {
if err = d.Set("oidc_groups_claim", groupsClain); err != nil {
return fmt.Errorf("%s error setting oidc_groups_claim: %v", errorPrefix, err)
}
}

// Set signing algs if it is set
if signingAlgs, isSigningAlgsSet := props.Parameters["k8s_oidc_signing_algs"]; isSigningAlgsSet {
if err = d.Set("oidc_signing_algs", signingAlgs); err != nil {
return fmt.Errorf("%s error setting oidc_signing_algs: %v", errorPrefix, err)
}
}

// Set groups prefix if it is set
if groupsPrefix, isGroupsPrefixSet := props.Parameters["k8s_oidc_groups_prefix"]; isGroupsPrefixSet {
if err = d.Set("oidc_groups_prefix", groupsPrefix); err != nil {
return fmt.Errorf("%s error setting oidc_groups_prefix: %v", errorPrefix, err)
}
}

// Set username prefix if it is set
if usernamePrefix, isUsernamePrefixSet := props.Parameters["k8s_oidc_username_prefix"]; isUsernamePrefixSet {
if err = d.Set("oidc_username_prefix", usernamePrefix); err != nil {
return fmt.Errorf("%s error setting oidc_username_prefix: %v", errorPrefix, err)
}
}

// Set required claim if it is set
if requiredClain, isRequiredClaimSet := props.Parameters["k8s_oidc_required_claim"]; isRequiredClaimSet {
if err = d.Set("oidc_required_claim", requiredClain); err != nil {
return fmt.Errorf("%s error setting oidc_required_claim: %v", errorPrefix, err)
}
}

// Set CA PEM if it is set
if caPEM, isCAPEMSet := props.Parameters["k8s_oidc_ca_pem"]; isCAPEMSet {
if err = d.Set("oidc_ca_pem", caPEM); err != nil {
return fmt.Errorf("%s error setting oidc_ca_pem: %v", errorPrefix, err)
}
}

//Get listen ports
listenPorts := make([]interface{}, 0)
for _, value := range props.ListenPorts {
Expand Down Expand Up @@ -450,6 +582,46 @@ func resourceGridscaleK8sCreate(d *schema.ResourceData, meta interface{}) error
if clusterTrafficEncryption, isSet := d.GetOk("node_pool.0.cluster_traffic_encryption"); isSet {
params["k8s_cluster_traffic_encryption"] = clusterTrafficEncryption
}
// Set OIDC enabled flag if it is set
if oidcEnabled, isOIDCEnabledSet := d.GetOk("oidc_enabled"); isOIDCEnabledSet {
params["k8s_oidc_enabled"] = oidcEnabled
}
// Set OIDC issuer URL if it is set
if oidcIssuerURL, isOIDCIssuerURLSet := d.GetOk("oidc_issuer_url"); isOIDCIssuerURLSet {
params["k8s_oidc_issuer_url"] = oidcIssuerURL
}
// Set OIDC client ID if it is set
if oidcClientID, isOIDCClientIDSet := d.GetOk("oidc_client_id"); isOIDCClientIDSet {
params["k8s_oidc_client_id"] = oidcClientID
}
// Set OIDC username claim if it is set
if oidcUsernameClaim, isOIDCUsernameClaimSet := d.GetOk("oidc_username_claim"); isOIDCUsernameClaimSet {
params["k8s_oidc_username_claim"] = oidcUsernameClaim
}
// Set OIDC groups claim if it is set
if oidcGroupsClaim, isOIDCGroupsClaimSet := d.GetOk("oidc_groups_claim"); isOIDCGroupsClaimSet {
params["k8s_oidc_groups_claim"] = oidcGroupsClaim
}
// Set signing algs if it is set
if oidcSigningAlgs, isOIDCSigningAlgsSet := d.GetOk("oidc_signing_algs"); isOIDCSigningAlgsSet {
params["k8s_oidc_signing_algs"] = oidcSigningAlgs
}
// Set groups prefix if it is set
if oidcGroupsPrefix, isOIDCGroupsPrefixSet := d.GetOk("oidc_groups_prefix"); isOIDCGroupsPrefixSet {
params["k8s_oidc_groups_prefix"] = oidcGroupsPrefix
}
// Set username prefix if it is set
if oidcUsernamePrefix, isOIDCUsernamePrefixSet := d.GetOk("oidc_username_prefix"); isOIDCUsernamePrefixSet {
params["k8s_oidc_username_prefix"] = oidcUsernamePrefix
}
// Set OIDC required claim if it is set
if oidcRequiredClaim, isOIDCRequiredClaimSet := d.GetOk("oidc_required_claim"); isOIDCRequiredClaimSet {
params["k8s_oidc_required_claim"] = oidcRequiredClaim
}
// Set OIDC CA PEM if it is set
if oidcCAPEM, isOIDCCAPEMSet := d.GetOk("oidc_ca_pem"); isOIDCCAPEMSet {
params["k8s_oidc_ca_pem"] = oidcCAPEM
}
requestBody.Parameters = params

ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutCreate))
Expand Down Expand Up @@ -523,6 +695,46 @@ func resourceGridscaleK8sUpdate(d *schema.ResourceData, meta interface{}) error
if clusterTrafficEncryption, isSet := d.GetOk("node_pool.0.cluster_traffic_encryption"); isSet {
params["k8s_cluster_traffic_encryption"] = clusterTrafficEncryption
}
// Set OIDC enabled flag if it is set
if oidcEnabled, isOIDCEnabledSet := d.GetOk("oidc_enabled"); isOIDCEnabledSet {
params["k8s_oidc_enabled"] = oidcEnabled
}
// Set OIDC issuer URL if it is set
if oidcIssuerURL, isOIDCIssuerURLSet := d.GetOk("oidc_issuer_url"); isOIDCIssuerURLSet {
params["k8s_oidc_issuer_url"] = oidcIssuerURL
}
// Set OIDC client ID if it is set
if oidcClientID, isOIDCClientIDSet := d.GetOk("oidc_client_id"); isOIDCClientIDSet {
params["k8s_oidc_client_id"] = oidcClientID
}
// Set OIDC username claim if it is set
if oidcUsernameClaim, isOIDCUsernameClaimSet := d.GetOk("oidc_username_claim"); isOIDCUsernameClaimSet {
params["k8s_oidc_username_claim"] = oidcUsernameClaim
}
// Set OIDC groups claim if it is set
if oidcGroupsClaim, isOIDCGroupsClaimSet := d.GetOk("oidc_groups_claim"); isOIDCGroupsClaimSet {
params["k8s_oidc_groups_claim"] = oidcGroupsClaim
}
// Set signing algs if it is set
if oidcSigningAlgs, isOIDCSigningAlgsSet := d.GetOk("oidc_signing_algs"); isOIDCSigningAlgsSet {
params["k8s_oidc_signing_algs"] = oidcSigningAlgs
}
// Set groups prefix if it is set
if oidcGroupsPrefix, isOIDCGroupsPrefixSet := d.GetOk("oidc_groups_prefix"); isOIDCGroupsPrefixSet {
params["k8s_oidc_groups_prefix"] = oidcGroupsPrefix
}
// Set username prefix if it is set
if oidcUsernamePrefix, isOIDCUsernamePrefixSet := d.GetOk("oidc_username_prefix"); isOIDCUsernamePrefixSet {
params["k8s_oidc_username_prefix"] = oidcUsernamePrefix
}
// Set OIDC required claim if it is set
if oidcRequiredClaim, isOIDCRequiredClaimSet := d.GetOk("oidc_required_claim"); isOIDCRequiredClaimSet {
params["k8s_oidc_required_claim"] = oidcRequiredClaim
}
// Set OIDC CA PEM if it is set
if oidcCAPEM, isOIDCCAPEMSet := d.GetOk("oidc_ca_pem"); isOIDCCAPEMSet {
params["k8s_oidc_ca_pem"] = oidcCAPEM
}
requestBody.Parameters = params

ctx, cancel := context.WithTimeout(context.Background(), d.Timeout(schema.TimeoutUpdate))
Expand Down Expand Up @@ -704,6 +916,41 @@ func validateK8sParameters(d *schema.ResourceDiff, template gsclient.PaaSTemplat
}
}

if oidcIssuerURL, ok := d.GetOk("oidc_issuer_url"); ok {
if _, ok := template.Properties.ParametersSchema["k8s_oidc_issuer_url"]; ok {
validMode := regexp.MustCompile(`^https:\/\/.*`)
if !validMode.MatchString(oidcIssuerURL.(string)) {
errorMessages = append(errorMessages, fmt.Sprintf("Invalid OIDC 'issuer_url' value. Example value: '%s'\n", "https://example.io"))
}
}
}

oidcSigningAlgsScheme, oidcSigningAlgsOk := template.Properties.ParametersSchema["k8s_oidc_signing_algs"]
if oidcSigningAlgs, ok := d.GetOk("oidc_signing_algs"); ok && oidcSigningAlgsOk {
var isValid bool
for _, allowedValue := range oidcSigningAlgsScheme.Allowed {
if oidcSigningAlgs.(string) == allowedValue {
isValid = true
}
}
if !isValid {
errorMessages = append(errorMessages,
fmt.Sprintf("Invalid OIDC 'signing_algs' value. Value must be one of these:\n\t%s",
strings.Join(oidcSigningAlgsScheme.Allowed, "\n\t"),
),
)
}
}

if oidcCAPEM, ok := d.GetOk("oidc_ca_pem"); ok {
if _, ok := template.Properties.ParametersSchema["k8s_oidc_ca_pem"]; ok {
block, _ := pem.Decode([]byte(oidcCAPEM.(string)))
if block == nil {
return fmt.Errorf("invalid OIDC 'ca_pem' value, failed to parse to CA PEM")
}
}
}

if len(errorMessages) != 0 {
return errors.New(strings.Join(errorMessages, ""))
}
Expand Down
13 changes: 11 additions & 2 deletions gridscale/resource_gridscale_k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func testAccCheckResourceGridscaleK8sConfigBasic(name string) string {
return fmt.Sprintf(`
resource "gridscale_k8s" "foopaas" {
name = "%s"
release = "1.26"
release = "1.29"
node_pool {
name = "my_node_pool"
node_count = 2
Expand All @@ -53,6 +53,15 @@ resource "gridscale_k8s" "foopaas" {
storage_type = "storage_insane"
rocket_storage = 90
}
oidc_enabled = true
oidc_issuer_url = "https://sts.windows.net/fe4ac456-23a7-4841-a404-01fcb695412c/"
oidc_client_id = "015ad6ba-1da5-4958-be94-8d50fa37898f"
oidc_username_claim = "email"
oidc_groups_claim = "groups"
oidc_groups_prefix = "oidc:"
oidc_username_prefix = "oidc:"
oidc_required_claim = "family_name=8944e8db-fe55-4443-aee0-16adfe637e71,given_name=c892dd1d-b324-48e6-a7da-051a96fbee37"
oidc_ca_pem = "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----\n"
}
`, name)
}
Expand All @@ -61,7 +70,7 @@ func testAccCheckResourceGridscaleK8sConfigBasicUpdate() string {
return `
resource "gridscale_k8s" "foopaas" {
name = "newname"
release = "1.26"
release = "1.29"
node_pool {
name = "my_node_pool"
node_count = 2
Expand Down

0 comments on commit d68bba5

Please sign in to comment.