diff --git a/.gitignore b/.gitignore index acea8a96..62f3ac22 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ go.work.sum # Editor/IDE # .idea/ # .vscode/ + +# Claude Code cache +.claude/tsc-cache/ diff --git a/api/core/v1beta1/_assets/usr/local/bin/pwb-psql b/api/core/v1beta1/_assets/usr/local/bin/pwb-psql deleted file mode 100755 index 61b65aa1..00000000 --- a/api/core/v1beta1/_assets/usr/local/bin/pwb-psql +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -o errexit -set -o pipefail - -main() { - : "${DATABASE_CONF:=/mnt/secure-config/rstudio/database.conf}" - - local pguser pgpass pghost pgdatabase - - pguser="$(awk -F= '/^username/ { print $NF }' "${DATABASE_CONF}")" - pghost="$(awk -F= '/^host/ { print $NF }' "${DATABASE_CONF}")" - pgpass="$(printenv WORKBENCH_POSTGRES_PASSWORD)" - pgdatabase="$(awk -F= '/^database/ { print $NF }' "${DATABASE_CONF}")" - - apt update -y - apt install -y postgresql-client - exec psql -d "postgres://${pguser}:${pgpass}@${pghost}:5432/${pgdatabase}" -} - -main "${@}" diff --git a/api/core/v1beta1/connect_types.go b/api/core/v1beta1/connect_types.go index d6e02640..b9195b03 100644 --- a/api/core/v1beta1/connect_types.go +++ b/api/core/v1beta1/connect_types.go @@ -147,6 +147,22 @@ type ConnectSpec struct { // ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and // injects the secret as an environment variable to the sidecar. **EXPERIMENTAL** ChronicleSidecarProductApiKeyEnabled bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` + + // AuthenticatedRepos enables PPM authenticated repository access for Connect + // +optional + AuthenticatedRepos bool `json:"authenticatedRepos,omitempty"` + + // PPMAuthImage specifies the container image for PPM auth init/sidecar containers + // +optional + PPMAuthImage string `json:"ppmAuthImage,omitempty"` + + // PPMUrl specifies the PPM URL for authenticated repository access + // +optional + PPMUrl string `json:"ppmUrl,omitempty"` + + // PPMAuthAudience is the audience claim for the projected SA token used in PPM Identity Federation + // +optional + PPMAuthAudience string `json:"ppmAuthAudience,omitempty"` } // TODO: Validation should require Volume definition for off-host-execution... diff --git a/api/core/v1beta1/package_manager_config.go b/api/core/v1beta1/package_manager_config.go index b6dabf5d..eeb95c8c 100644 --- a/api/core/v1beta1/package_manager_config.go +++ b/api/core/v1beta1/package_manager_config.go @@ -7,17 +7,20 @@ import ( ) type PackageManagerConfig struct { - Server *PackageManagerServerConfig `json:"Server,omitempty"` - Http *PackageManagerHttpConfig `json:"Http,omitempty"` - Git *PackageManagerGitConfig `json:"Git,omitempty"` - Database *PackageManagerDatabaseConfig `json:"Database,omitempty"` - Postgres *PackageManagerPostgresConfig `json:"Postgres,omitempty"` - Storage *PackageManagerStorageConfig `json:"Storage,omitempty"` - S3Storage *PackageManagerS3StorageConfig `json:"S3Storage,omitempty"` - Metrics *PackageManagerMetricsConfig `json:"Metrics,omitempty"` - Repos *PackageManagerReposConfig `json:"Repos,omitempty"` - Cran *PackageManagerCRANConfig `json:"CRAN,omitempty"` - Debug *PackageManagerDebugConfig `json:"Debug,omitempty"` + Server *PackageManagerServerConfig `json:"Server,omitempty"` + Http *PackageManagerHttpConfig `json:"Http,omitempty"` + Git *PackageManagerGitConfig `json:"Git,omitempty"` + Database *PackageManagerDatabaseConfig `json:"Database,omitempty"` + Postgres *PackageManagerPostgresConfig `json:"Postgres,omitempty"` + Storage *PackageManagerStorageConfig `json:"Storage,omitempty"` + S3Storage *PackageManagerS3StorageConfig `json:"S3Storage,omitempty"` + Metrics *PackageManagerMetricsConfig `json:"Metrics,omitempty"` + Repos *PackageManagerReposConfig `json:"Repos,omitempty"` + Cran *PackageManagerCRANConfig `json:"CRAN,omitempty"` + Debug *PackageManagerDebugConfig `json:"Debug,omitempty"` + Authentication *PackageManagerAuthenticationConfig `json:"Authentication,omitempty"` + OpenIDConnect *PackageManagerOIDCConfig `json:"OpenIDConnect,omitempty"` + IdentityFederation []PackageManagerIdentityFederationConfig `json:"identityFederation,omitempty"` // AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. // The value is appended verbatim after the generated config. gcfg parsing naturally handles @@ -53,6 +56,11 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { continue } + // Skip IdentityFederation - handled specially after the main loop + if fieldName == "IdentityFederation" { + continue + } + if fieldValue.IsNil() { continue } @@ -107,6 +115,59 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { } } + // Render named IdentityFederation sections (these use the gcfg named subsection syntax) + for _, idf := range configStruct.IdentityFederation { + if strings.ContainsAny(idf.Name, "\"]\n") { + return "", fmt.Errorf("invalid IdentityFederation name %q: must not contain '\"', ']', or newlines", idf.Name) + } + builder.WriteString(fmt.Sprintf("\n[IdentityFederation \"%s\"]\n", idf.Name)) + if idf.Issuer != "" { + builder.WriteString("Issuer = " + idf.Issuer + "\n") + } + if idf.Logging { + builder.WriteString("Logging = true\n") + } + if idf.Audience != "" { + builder.WriteString("Audience = " + idf.Audience + "\n") + } + if idf.Subject != "" { + builder.WriteString("Subject = " + idf.Subject + "\n") + } + if idf.AuthorizedParty != "" { + builder.WriteString("AuthorizedParty = " + idf.AuthorizedParty + "\n") + } + if idf.Scope != "" { + builder.WriteString("Scope = " + idf.Scope + "\n") + } + if idf.CustomScope != "" { + builder.WriteString("CustomScope = " + idf.CustomScope + "\n") + } + if idf.NoAutoGroupsScope { + builder.WriteString("NoAutoGroupsScope = true\n") + } + if idf.GroupsClaim != "" { + builder.WriteString("GroupsClaim = " + idf.GroupsClaim + "\n") + } + if idf.GroupsSeparator != "" { + builder.WriteString("GroupsSeparator = " + idf.GroupsSeparator + "\n") + } + if idf.RoleClaim != "" { + builder.WriteString("RoleClaim = " + idf.RoleClaim + "\n") + } + if idf.RolesSeparator != "" { + builder.WriteString("RolesSeparator = " + idf.RolesSeparator + "\n") + } + if idf.UniqueIdClaim != "" { + builder.WriteString("UniqueIdClaim = " + idf.UniqueIdClaim + "\n") + } + if idf.UsernameClaim != "" { + builder.WriteString("UsernameClaim = " + idf.UsernameClaim + "\n") + } + if idf.TokenLifetime != "" { + builder.WriteString("TokenLifetime = " + idf.TokenLifetime + "\n") + } + } + if configStruct.AdditionalConfig != "" { builder.WriteString(configStruct.AdditionalConfig) } @@ -176,6 +237,56 @@ type PackageManagerDebugConfig struct { Log string `json:"Log,omitempty"` } +type PackageManagerAuthenticationConfig struct { + APITokenAuth bool `json:"APITokenAuth,omitempty"` + DeviceAuthType string `json:"DeviceAuthType,omitempty"` + NewReposAuthByDefault bool `json:"NewReposAuthByDefault,omitempty"` + Lifetime string `json:"Lifetime,omitempty"` + Inactivity string `json:"Inactivity,omitempty"` + CookieSweepDuration string `json:"CookieSweepDuration,omitempty"` +} + +type PackageManagerOIDCConfig struct { + ClientId string `json:"ClientId,omitempty"` + ClientSecret string `json:"ClientSecret,omitempty"` + ClientSecretFile string `json:"ClientSecretFile,omitempty"` + Issuer string `json:"Issuer,omitempty"` + RequireLogin bool `json:"RequireLogin,omitempty"` + Logging bool `json:"Logging,omitempty"` + Scope string `json:"Scope,omitempty"` + CustomScope string `json:"CustomScope,omitempty"` + NoAutoGroupsScope bool `json:"NoAutoGroupsScope,omitempty"` + GroupsClaim string `json:"GroupsClaim,omitempty"` + GroupsSeparator string `json:"GroupsSeparator,omitempty"` + RoleClaim string `json:"RoleClaim,omitempty"` + RolesSeparator string `json:"RolesSeparator,omitempty"` + UniqueIdClaim string `json:"UniqueIdClaim,omitempty"` + UsernameClaim string `json:"UsernameClaim,omitempty"` + TokenLifetime string `json:"TokenLifetime,omitempty"` + MaxAuthenticationAge string `json:"MaxAuthenticationAge,omitempty"` + DisablePKCE bool `json:"DisablePKCE,omitempty"` + EnableDevicePKCE bool `json:"EnableDevicePKCE,omitempty"` +} + +type PackageManagerIdentityFederationConfig struct { + Name string `json:"name,omitempty"` + Issuer string `json:"Issuer,omitempty"` + Logging bool `json:"Logging,omitempty"` + Audience string `json:"Audience,omitempty"` + Subject string `json:"Subject,omitempty"` + AuthorizedParty string `json:"AuthorizedParty,omitempty"` + Scope string `json:"Scope,omitempty"` + CustomScope string `json:"CustomScope,omitempty"` + NoAutoGroupsScope bool `json:"NoAutoGroupsScope,omitempty"` + GroupsClaim string `json:"GroupsClaim,omitempty"` + GroupsSeparator string `json:"GroupsSeparator,omitempty"` + RoleClaim string `json:"RoleClaim,omitempty"` + RolesSeparator string `json:"RolesSeparator,omitempty"` + UniqueIdClaim string `json:"UniqueIdClaim,omitempty"` + UsernameClaim string `json:"UsernameClaim,omitempty"` + TokenLifetime string `json:"TokenLifetime,omitempty"` +} + // SSHKeyConfig defines SSH key configuration for Git authentication type SSHKeyConfig struct { // Name is a unique identifier for this SSH key configuration diff --git a/api/core/v1beta1/package_manager_config_test.go b/api/core/v1beta1/package_manager_config_test.go index ddf37a91..8c021c0d 100644 --- a/api/core/v1beta1/package_manager_config_test.go +++ b/api/core/v1beta1/package_manager_config_test.go @@ -62,3 +62,177 @@ func TestPackageManagerConfig_AdditionalConfigEmpty(t *testing.T) { require.Nil(t, err) require.Contains(t, str, "Address = some-address.com") } + +func TestPackageManagerConfig_OpenIDConnect(t *testing.T) { + cfg := PackageManagerConfig{ + OpenIDConnect: &PackageManagerOIDCConfig{ + ClientId: "my-client-id", + ClientSecret: "/etc/rstudio-pm/oidc-client-secret", + Issuer: "https://login.example.com", + RequireLogin: true, + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[OpenIDConnect]") + require.Contains(t, str, "ClientId = my-client-id") + require.Contains(t, str, "ClientSecret = /etc/rstudio-pm/oidc-client-secret") + require.Contains(t, str, "Issuer = https://login.example.com") + require.Contains(t, str, "RequireLogin = true") +} + +func TestPackageManagerConfig_IdentityFederation(t *testing.T) { + cfg := PackageManagerConfig{ + IdentityFederation: []PackageManagerIdentityFederationConfig{ + { + Name: "connect", + Issuer: "https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE", + Audience: "sts.amazonaws.com", + Subject: "system:serviceaccount:posit-team:mysite-connect", + Scope: "repos:read:*", + }, + { + Name: "workbench", + Issuer: "https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE", + Audience: "sts.amazonaws.com", + Subject: "system:serviceaccount:posit-team:mysite-workbench", + Scope: "repos:read:*", + }, + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, `[IdentityFederation "connect"]`) + require.Contains(t, str, `[IdentityFederation "workbench"]`) + require.Contains(t, str, "Issuer = https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE") + require.Contains(t, str, "Audience = sts.amazonaws.com") + require.Contains(t, str, "Subject = system:serviceaccount:posit-team:mysite-connect") + require.Contains(t, str, "Scope = repos:read:*") +} + +func TestPackageManagerConfig_OpenIDConnectAndIdentityFederation(t *testing.T) { + cfg := PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + Address: "https://packagemanager.example.com", + }, + OpenIDConnect: &PackageManagerOIDCConfig{ + ClientId: "ppm-client", + ClientSecret: "/etc/rstudio-pm/oidc-client-secret", + Issuer: "https://login.example.com", + RequireLogin: true, + }, + IdentityFederation: []PackageManagerIdentityFederationConfig{ + { + Name: "connect", + Issuer: "https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE", + Audience: "sts.amazonaws.com", + Subject: "system:serviceaccount:posit-team:.*-connect", + Scope: "repos:read:*", + TokenLifetime: "1h", + }, + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[Server]") + require.Contains(t, str, "[OpenIDConnect]") + require.Contains(t, str, `[IdentityFederation "connect"]`) + require.Contains(t, str, "TokenLifetime = 1h") +} + +func TestPackageManagerConfig_Authentication(t *testing.T) { + cfg := PackageManagerConfig{ + Authentication: &PackageManagerAuthenticationConfig{ + APITokenAuth: true, + DeviceAuthType: "oidc", + NewReposAuthByDefault: true, + Lifetime: "30d", + Inactivity: "12h", + CookieSweepDuration: "5m", + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[Authentication]") + require.Contains(t, str, "APITokenAuth = true") + require.Contains(t, str, "DeviceAuthType = oidc") + require.Contains(t, str, "NewReposAuthByDefault = true") + require.Contains(t, str, "Lifetime = 30d") + require.Contains(t, str, "Inactivity = 12h") + require.Contains(t, str, "CookieSweepDuration = 5m") +} + +func TestPackageManagerConfig_OIDCNewFields(t *testing.T) { + cfg := PackageManagerConfig{ + OpenIDConnect: &PackageManagerOIDCConfig{ + ClientId: "my-client", + ClientSecretFile: "/etc/rstudio-pm/oidc-secret", + Issuer: "https://auth.example.com", + Logging: true, + TokenLifetime: "1h", + DisablePKCE: true, + UniqueIdClaim: "sub", + UsernameClaim: "preferred_username", + MaxAuthenticationAge: "24h", + GroupsSeparator: ",", + RolesSeparator: ";", + CustomScope: "profile email groups", + NoAutoGroupsScope: true, + EnableDevicePKCE: true, + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[OpenIDConnect]") + require.Contains(t, str, "ClientId = my-client") + require.Contains(t, str, "ClientSecretFile = /etc/rstudio-pm/oidc-secret") + require.Contains(t, str, "Issuer = https://auth.example.com") + require.Contains(t, str, "Logging = true") + require.Contains(t, str, "TokenLifetime = 1h") + require.Contains(t, str, "DisablePKCE = true") + require.Contains(t, str, "UniqueIdClaim = sub") + require.Contains(t, str, "UsernameClaim = preferred_username") + require.Contains(t, str, "MaxAuthenticationAge = 24h") + require.Contains(t, str, "GroupsSeparator = ,") + require.Contains(t, str, "RolesSeparator = ;") + require.Contains(t, str, "CustomScope = profile email groups") + require.Contains(t, str, "NoAutoGroupsScope = true") + require.Contains(t, str, "EnableDevicePKCE = true") +} + +func TestPackageManagerConfig_IdentityFederationNewFields(t *testing.T) { + cfg := PackageManagerConfig{ + IdentityFederation: []PackageManagerIdentityFederationConfig{ + { + Name: "my-idp", + Issuer: "https://issuer.example.com", + Logging: true, + Audience: "my-audience", + CustomScope: "read write", + NoAutoGroupsScope: true, + GroupsClaim: "groups", + GroupsSeparator: ",", + RoleClaim: "roles", + RolesSeparator: ";", + UniqueIdClaim: "sub", + UsernameClaim: "preferred_username", + TokenLifetime: "2h", + }, + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, `[IdentityFederation "my-idp"]`) + require.Contains(t, str, "Issuer = https://issuer.example.com") + require.Contains(t, str, "Logging = true") + require.Contains(t, str, "Audience = my-audience") + require.Contains(t, str, "CustomScope = read write") + require.Contains(t, str, "NoAutoGroupsScope = true") + require.Contains(t, str, "GroupsClaim = groups") + require.Contains(t, str, "GroupsSeparator = ,") + require.Contains(t, str, "RoleClaim = roles") + require.Contains(t, str, "RolesSeparator = ;") + require.Contains(t, str, "UniqueIdClaim = sub") + require.Contains(t, str, "UsernameClaim = preferred_username") + require.Contains(t, str, "TokenLifetime = 2h") +} diff --git a/api/core/v1beta1/packagemanager_types.go b/api/core/v1beta1/packagemanager_types.go index 0163572b..9ea5a9fc 100644 --- a/api/core/v1beta1/packagemanager_types.go +++ b/api/core/v1beta1/packagemanager_types.go @@ -79,6 +79,11 @@ type PackageManagerSpec struct { // AzureFiles configures Azure Files integration for persistent storage // +optional AzureFiles *AzureFilesConfig `json:"azureFiles,omitempty"` + + // OIDCClientSecretKey is the key name in the vault for the OIDC client secret. + // When set, the client secret will be mounted at /etc/rstudio-pm/oidc-client-secret + // +optional + OIDCClientSecretKey string `json:"oidcClientSecretKey,omitempty"` } // PackageManagerStatus defines the observed state of PackageManager @@ -313,6 +318,24 @@ func (pm *PackageManager) CreateSecretVolumeFactory() *product.SecretVolumeFacto } } + // Add OIDC client secret volume mount if configured + if pm.Spec.OIDCClientSecretKey != "" { + vols["client-secret-volume"] = &product.VolumeDef{ + Source: &v1.VolumeSource{ + CSI: &v1.CSIVolumeSource{ + Driver: "secrets-store.csi.k8s.io", + ReadOnly: ptr.To(true), + VolumeAttributes: map[string]string{ + "secretProviderClass": pm.SecretProviderClassName(), + }, + }, + }, + Mounts: []*product.VolumeMountDef{ + {MountPath: "/etc/rstudio-pm/oidc-client-secret", SubPath: "oidc-client-secret", ReadOnly: true}, + }, + } + } + case product.SiteSecretKubernetes: vols["key-volume"] = &product.VolumeDef{ Env: []v1.EnvVar{ @@ -350,6 +373,23 @@ func (pm *PackageManager) CreateSecretVolumeFactory() *product.SecretVolumeFacto }, } + // Add OIDC client secret volume mount if configured + if pm.Spec.OIDCClientSecretKey != "" { + vols["client-secret-volume"] = &product.VolumeDef{ + Source: &v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: pm.GetSecretVaultName(), + Items: []v1.KeyToPath{ + {Key: pm.Spec.OIDCClientSecretKey, Path: "oidc-client-secret"}, + }, + }, + }, + Mounts: []*product.VolumeMountDef{ + {MountPath: "/etc/rstudio-pm/oidc-client-secret", SubPath: "oidc-client-secret", ReadOnly: true}, + }, + } + } + default: // uh oh... some other type of secret...? } diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index 44bb4261..b0d9bad5 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -125,6 +125,15 @@ type SiteSpec struct { // Defaults to true. // +kubebuilder:default=true EnableFQDNHealthChecks *bool `json:"enableFqdnHealthChecks,omitempty"` + + // OIDCIssuerURL is the K8s cluster OIDC issuer URL (for EKS/AKS Identity Federation) + // +optional + OIDCIssuerURL string `json:"oidcIssuerUrl,omitempty"` + + // OIDCAudience is the audience claim for the OIDC token used in Identity Federation. + // For EKS this is typically "sts.amazonaws.com", for AKS it varies by configuration. + // +optional + OIDCAudience string `json:"oidcAudience,omitempty"` } type ServiceAccountConfig struct { @@ -225,6 +234,14 @@ type InternalPackageManagerSpec struct { // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. // +optional AdditionalConfig string `json:"additionalConfig,omitempty"` + + // Auth configures OIDC authentication for Package Manager's web UI + // +optional + Auth *AuthSpec `json:"auth,omitempty"` + + // OIDCClientSecretKey is the key in the vault for the OIDC client secret + // +optional + OIDCClientSecretKey string `json:"oidcClientSecretKey,omitempty"` } type InternalConnectSpec struct { @@ -298,6 +315,14 @@ type InternalConnectSpec struct { // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. // +optional AdditionalConfig string `json:"additionalConfig,omitempty"` + + // AuthenticatedRepos enables PPM authenticated repository access for Connect + // +optional + AuthenticatedRepos bool `json:"authenticatedRepos,omitempty"` + + // PPMAuthImage specifies the container image for PPM auth init/sidecar containers + // +optional + PPMAuthImage string `json:"ppmAuthImage,omitempty"` } type DatabaseSettings struct { @@ -435,6 +460,14 @@ type InternalWorkbenchSpec struct { // Keys are config file names (e.g., "rsession.conf", "repos.conf"). // +optional AdditionalSessionConfigs map[string]string `json:"additionalSessionConfigs,omitempty"` + + // AuthenticatedRepos enables PPM authenticated repository access for Workbench + // +optional + AuthenticatedRepos bool `json:"authenticatedRepos,omitempty"` + + // PPMAuthImage specifies the container image for PPM auth init/sidecar containers + // +optional + PPMAuthImage string `json:"ppmAuthImage,omitempty"` } type InternalWorkbenchExperimentalFeatures struct { diff --git a/api/core/v1beta1/workbench_types.go b/api/core/v1beta1/workbench_types.go index 00cf0dbe..d8289aa4 100644 --- a/api/core/v1beta1/workbench_types.go +++ b/api/core/v1beta1/workbench_types.go @@ -109,6 +109,22 @@ type WorkbenchSpec struct { // Empty or whitespace-only content will be ignored. // See: https://docs.posit.co/ide/server-pro/admin/authenticating_users/customizing_signin.html AuthLoginPageHtml string `json:"authLoginPageHtml,omitempty"` + + // AuthenticatedRepos enables PPM authenticated repository access for Workbench + // +optional + AuthenticatedRepos bool `json:"authenticatedRepos,omitempty"` + + // PPMAuthImage specifies the container image for PPM auth init/sidecar containers + // +optional + PPMAuthImage string `json:"ppmAuthImage,omitempty"` + + // PPMUrl specifies the PPM URL for authenticated repository access + // +optional + PPMUrl string `json:"ppmUrl,omitempty"` + + // PPMAuthAudience is the audience claim for the projected SA token used in PPM Identity Federation + // +optional + PPMAuthAudience string `json:"ppmAuthAudience,omitempty"` } // TODO: Validation should require Volume definition for off-host-execution... diff --git a/api/core/v1beta1/zz_generated.deepcopy.go b/api/core/v1beta1/zz_generated.deepcopy.go index dc0775bd..b4bda363 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -1302,6 +1302,11 @@ func (in *InternalPackageManagerSpec) DeepCopyInto(out *InternalPackageManagerSp *out = new(AzureFilesConfig) **out = **in } + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(AuthSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalPackageManagerSpec. @@ -1542,6 +1547,21 @@ func (in *PackageManager) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PackageManagerAuthenticationConfig) DeepCopyInto(out *PackageManagerAuthenticationConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerAuthenticationConfig. +func (in *PackageManagerAuthenticationConfig) DeepCopy() *PackageManagerAuthenticationConfig { + if in == nil { + return nil + } + out := new(PackageManagerAuthenticationConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageManagerCRANConfig) DeepCopyInto(out *PackageManagerCRANConfig) { *out = *in @@ -1615,6 +1635,21 @@ func (in *PackageManagerConfig) DeepCopyInto(out *PackageManagerConfig) { *out = new(PackageManagerDebugConfig) **out = **in } + if in.Authentication != nil { + in, out := &in.Authentication, &out.Authentication + *out = new(PackageManagerAuthenticationConfig) + **out = **in + } + if in.OpenIDConnect != nil { + in, out := &in.OpenIDConnect, &out.OpenIDConnect + *out = new(PackageManagerOIDCConfig) + **out = **in + } + if in.IdentityFederation != nil { + in, out := &in.IdentityFederation, &out.IdentityFederation + *out = make([]PackageManagerIdentityFederationConfig, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerConfig. @@ -1687,6 +1722,21 @@ func (in *PackageManagerHttpConfig) DeepCopy() *PackageManagerHttpConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PackageManagerIdentityFederationConfig) DeepCopyInto(out *PackageManagerIdentityFederationConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerIdentityFederationConfig. +func (in *PackageManagerIdentityFederationConfig) DeepCopy() *PackageManagerIdentityFederationConfig { + if in == nil { + return nil + } + out := new(PackageManagerIdentityFederationConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageManagerList) DeepCopyInto(out *PackageManagerList) { *out = *in @@ -1734,6 +1784,21 @@ func (in *PackageManagerMetricsConfig) DeepCopy() *PackageManagerMetricsConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PackageManagerOIDCConfig) DeepCopyInto(out *PackageManagerOIDCConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerOIDCConfig. +func (in *PackageManagerOIDCConfig) DeepCopy() *PackageManagerOIDCConfig { + if in == nil { + return nil + } + out := new(PackageManagerOIDCConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageManagerPostgresConfig) DeepCopyInto(out *PackageManagerPostgresConfig) { *out = *in diff --git a/client-go/applyconfiguration/core/v1beta1/connectspec.go b/client-go/applyconfiguration/core/v1beta1/connectspec.go index 6cd407ff..e327b5c4 100644 --- a/client-go/applyconfiguration/core/v1beta1/connectspec.go +++ b/client-go/applyconfiguration/core/v1beta1/connectspec.go @@ -46,6 +46,10 @@ type ConnectSpecApplyConfiguration struct { Replicas *int `json:"replicas,omitempty"` DsnSecret *string `json:"dsnSecret,omitempty"` ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` + AuthenticatedRepos *bool `json:"authenticatedRepos,omitempty"` + PPMAuthImage *string `json:"ppmAuthImage,omitempty"` + PPMUrl *string `json:"ppmUrl,omitempty"` + PPMAuthAudience *string `json:"ppmAuthAudience,omitempty"` } // ConnectSpecApplyConfiguration constructs a declarative configuration of the ConnectSpec type for use with @@ -344,3 +348,35 @@ func (b *ConnectSpecApplyConfiguration) WithChronicleSidecarProductApiKeyEnabled b.ChronicleSidecarProductApiKeyEnabled = &value return b } + +// WithAuthenticatedRepos sets the AuthenticatedRepos field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthenticatedRepos field is set to the value of the last call. +func (b *ConnectSpecApplyConfiguration) WithAuthenticatedRepos(value bool) *ConnectSpecApplyConfiguration { + b.AuthenticatedRepos = &value + return b +} + +// WithPPMAuthImage sets the PPMAuthImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthImage field is set to the value of the last call. +func (b *ConnectSpecApplyConfiguration) WithPPMAuthImage(value string) *ConnectSpecApplyConfiguration { + b.PPMAuthImage = &value + return b +} + +// WithPPMUrl sets the PPMUrl field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMUrl field is set to the value of the last call. +func (b *ConnectSpecApplyConfiguration) WithPPMUrl(value string) *ConnectSpecApplyConfiguration { + b.PPMUrl = &value + return b +} + +// WithPPMAuthAudience sets the PPMAuthAudience field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthAudience field is set to the value of the last call. +func (b *ConnectSpecApplyConfiguration) WithPPMAuthAudience(value string) *ConnectSpecApplyConfiguration { + b.PPMAuthAudience = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go index 94629195..9a0f3819 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go @@ -36,6 +36,8 @@ type InternalConnectSpecApplyConfiguration struct { ScheduleConcurrency *int `json:"scheduleConcurrency,omitempty"` AdditionalRuntimeImages []ConnectRuntimeImageSpecApplyConfiguration `json:"additionalRuntimeImages,omitempty"` AdditionalConfig *string `json:"additionalConfig,omitempty"` + AuthenticatedRepos *bool `json:"authenticatedRepos,omitempty"` + PPMAuthImage *string `json:"ppmAuthImage,omitempty"` } // InternalConnectSpecApplyConfiguration constructs a declarative configuration of the InternalConnectSpec type for use with @@ -244,3 +246,19 @@ func (b *InternalConnectSpecApplyConfiguration) WithAdditionalConfig(value strin b.AdditionalConfig = &value return b } + +// WithAuthenticatedRepos sets the AuthenticatedRepos field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthenticatedRepos field is set to the value of the last call. +func (b *InternalConnectSpecApplyConfiguration) WithAuthenticatedRepos(value bool) *InternalConnectSpecApplyConfiguration { + b.AuthenticatedRepos = &value + return b +} + +// WithPPMAuthImage sets the PPMAuthImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthImage field is set to the value of the last call. +func (b *InternalConnectSpecApplyConfiguration) WithPPMAuthImage(value string) *InternalConnectSpecApplyConfiguration { + b.PPMAuthImage = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go index c9e12970..16a5df92 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go @@ -13,19 +13,21 @@ import ( // InternalPackageManagerSpecApplyConfiguration represents a declarative configuration of the InternalPackageManagerSpec type for use // with apply. type InternalPackageManagerSpecApplyConfiguration struct { - License *product.LicenseSpec `json:"license,omitempty"` - Volume *product.VolumeSpec `json:"volume,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - AddEnv map[string]string `json:"addEnv,omitempty"` - Image *string `json:"image,omitempty"` - ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"` - S3Bucket *string `json:"s3Bucket,omitempty"` - Replicas *int `json:"replicas,omitempty"` - DomainPrefix *string `json:"domainPrefix,omitempty"` - BaseDomain *string `json:"baseDomain,omitempty"` - GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` - AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` - AdditionalConfig *string `json:"additionalConfig,omitempty"` + License *product.LicenseSpec `json:"license,omitempty"` + Volume *product.VolumeSpec `json:"volume,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + AddEnv map[string]string `json:"addEnv,omitempty"` + Image *string `json:"image,omitempty"` + ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"` + S3Bucket *string `json:"s3Bucket,omitempty"` + Replicas *int `json:"replicas,omitempty"` + DomainPrefix *string `json:"domainPrefix,omitempty"` + BaseDomain *string `json:"baseDomain,omitempty"` + GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` + AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` + Auth *AuthSpecApplyConfiguration `json:"auth,omitempty"` + OIDCClientSecretKey *string `json:"oidcClientSecretKey,omitempty"` } // InternalPackageManagerSpecApplyConfiguration constructs a declarative configuration of the InternalPackageManagerSpec type for use with @@ -154,3 +156,19 @@ func (b *InternalPackageManagerSpecApplyConfiguration) WithAdditionalConfig(valu b.AdditionalConfig = &value return b } + +// WithAuth sets the Auth field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Auth field is set to the value of the last call. +func (b *InternalPackageManagerSpecApplyConfiguration) WithAuth(value *AuthSpecApplyConfiguration) *InternalPackageManagerSpecApplyConfiguration { + b.Auth = value + return b +} + +// WithOIDCClientSecretKey sets the OIDCClientSecretKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OIDCClientSecretKey field is set to the value of the last call. +func (b *InternalPackageManagerSpecApplyConfiguration) WithOIDCClientSecretKey(value string) *InternalPackageManagerSpecApplyConfiguration { + b.OIDCClientSecretKey = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go b/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go index 1776cfc0..fa05eeaa 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go @@ -47,6 +47,8 @@ type InternalWorkbenchSpecApplyConfiguration struct { JupyterConfig *WorkbenchJupyterConfigApplyConfiguration `json:"jupyterConfig,omitempty"` AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` AdditionalSessionConfigs map[string]string `json:"additionalSessionConfigs,omitempty"` + AuthenticatedRepos *bool `json:"authenticatedRepos,omitempty"` + PPMAuthImage *string `json:"ppmAuthImage,omitempty"` } // InternalWorkbenchSpecApplyConfiguration constructs a declarative configuration of the InternalWorkbenchSpec type for use with @@ -368,3 +370,19 @@ func (b *InternalWorkbenchSpecApplyConfiguration) WithAdditionalSessionConfigs(e } return b } + +// WithAuthenticatedRepos sets the AuthenticatedRepos field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthenticatedRepos field is set to the value of the last call. +func (b *InternalWorkbenchSpecApplyConfiguration) WithAuthenticatedRepos(value bool) *InternalWorkbenchSpecApplyConfiguration { + b.AuthenticatedRepos = &value + return b +} + +// WithPPMAuthImage sets the PPMAuthImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthImage field is set to the value of the last call. +func (b *InternalWorkbenchSpecApplyConfiguration) WithPPMAuthImage(value string) *InternalWorkbenchSpecApplyConfiguration { + b.PPMAuthImage = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanagerauthenticationconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanagerauthenticationconfig.go new file mode 100644 index 00000000..24fca6f5 --- /dev/null +++ b/client-go/applyconfiguration/core/v1beta1/packagemanagerauthenticationconfig.go @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023-2026 Posit Software, PBC + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +// PackageManagerAuthenticationConfigApplyConfiguration represents a declarative configuration of the PackageManagerAuthenticationConfig type for use +// with apply. +type PackageManagerAuthenticationConfigApplyConfiguration struct { + APITokenAuth *bool `json:"APITokenAuth,omitempty"` + DeviceAuthType *string `json:"DeviceAuthType,omitempty"` + NewReposAuthByDefault *bool `json:"NewReposAuthByDefault,omitempty"` + Lifetime *string `json:"Lifetime,omitempty"` + Inactivity *string `json:"Inactivity,omitempty"` + CookieSweepDuration *string `json:"CookieSweepDuration,omitempty"` +} + +// PackageManagerAuthenticationConfigApplyConfiguration constructs a declarative configuration of the PackageManagerAuthenticationConfig type for use with +// apply. +func PackageManagerAuthenticationConfig() *PackageManagerAuthenticationConfigApplyConfiguration { + return &PackageManagerAuthenticationConfigApplyConfiguration{} +} + +// WithAPITokenAuth sets the APITokenAuth field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APITokenAuth field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithAPITokenAuth(value bool) *PackageManagerAuthenticationConfigApplyConfiguration { + b.APITokenAuth = &value + return b +} + +// WithDeviceAuthType sets the DeviceAuthType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeviceAuthType field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithDeviceAuthType(value string) *PackageManagerAuthenticationConfigApplyConfiguration { + b.DeviceAuthType = &value + return b +} + +// WithNewReposAuthByDefault sets the NewReposAuthByDefault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NewReposAuthByDefault field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithNewReposAuthByDefault(value bool) *PackageManagerAuthenticationConfigApplyConfiguration { + b.NewReposAuthByDefault = &value + return b +} + +// WithLifetime sets the Lifetime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Lifetime field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithLifetime(value string) *PackageManagerAuthenticationConfigApplyConfiguration { + b.Lifetime = &value + return b +} + +// WithInactivity sets the Inactivity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Inactivity field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithInactivity(value string) *PackageManagerAuthenticationConfigApplyConfiguration { + b.Inactivity = &value + return b +} + +// WithCookieSweepDuration sets the CookieSweepDuration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CookieSweepDuration field is set to the value of the last call. +func (b *PackageManagerAuthenticationConfigApplyConfiguration) WithCookieSweepDuration(value string) *PackageManagerAuthenticationConfigApplyConfiguration { + b.CookieSweepDuration = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go index c853572d..7e9ba640 100644 --- a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go @@ -8,18 +8,21 @@ package v1beta1 // PackageManagerConfigApplyConfiguration represents a declarative configuration of the PackageManagerConfig type for use // with apply. type PackageManagerConfigApplyConfiguration struct { - Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` - Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` - Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` - Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` - Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` - Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` - S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` - Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` - Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` - Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` - Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` - AdditionalConfig *string `json:"additionalConfig,omitempty"` + Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` + Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` + Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` + Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` + Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` + Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` + S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` + Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` + Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` + Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` + Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` + Authentication *PackageManagerAuthenticationConfigApplyConfiguration `json:"Authentication,omitempty"` + OpenIDConnect *PackageManagerOIDCConfigApplyConfiguration `json:"OpenIDConnect,omitempty"` + IdentityFederation []PackageManagerIdentityFederationConfigApplyConfiguration `json:"identityFederation,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // PackageManagerConfigApplyConfiguration constructs a declarative configuration of the PackageManagerConfig type for use with @@ -116,6 +119,35 @@ func (b *PackageManagerConfigApplyConfiguration) WithDebug(value *PackageManager return b } +// WithAuthentication sets the Authentication field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Authentication field is set to the value of the last call. +func (b *PackageManagerConfigApplyConfiguration) WithAuthentication(value *PackageManagerAuthenticationConfigApplyConfiguration) *PackageManagerConfigApplyConfiguration { + b.Authentication = value + return b +} + +// WithOpenIDConnect sets the OpenIDConnect field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OpenIDConnect field is set to the value of the last call. +func (b *PackageManagerConfigApplyConfiguration) WithOpenIDConnect(value *PackageManagerOIDCConfigApplyConfiguration) *PackageManagerConfigApplyConfiguration { + b.OpenIDConnect = value + return b +} + +// WithIdentityFederation adds the given value to the IdentityFederation field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IdentityFederation field. +func (b *PackageManagerConfigApplyConfiguration) WithIdentityFederation(values ...*PackageManagerIdentityFederationConfigApplyConfiguration) *PackageManagerConfigApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithIdentityFederation") + } + b.IdentityFederation = append(b.IdentityFederation, *values[i]) + } + return b +} + // WithAdditionalConfig sets the AdditionalConfig field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AdditionalConfig field is set to the value of the last call. diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanageridentityfederationconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanageridentityfederationconfig.go new file mode 100644 index 00000000..32072f85 --- /dev/null +++ b/client-go/applyconfiguration/core/v1beta1/packagemanageridentityfederationconfig.go @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023-2026 Posit Software, PBC + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +// PackageManagerIdentityFederationConfigApplyConfiguration represents a declarative configuration of the PackageManagerIdentityFederationConfig type for use +// with apply. +type PackageManagerIdentityFederationConfigApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Issuer *string `json:"Issuer,omitempty"` + Logging *bool `json:"Logging,omitempty"` + Audience *string `json:"Audience,omitempty"` + Subject *string `json:"Subject,omitempty"` + AuthorizedParty *string `json:"AuthorizedParty,omitempty"` + Scope *string `json:"Scope,omitempty"` + CustomScope *string `json:"CustomScope,omitempty"` + NoAutoGroupsScope *bool `json:"NoAutoGroupsScope,omitempty"` + GroupsClaim *string `json:"GroupsClaim,omitempty"` + GroupsSeparator *string `json:"GroupsSeparator,omitempty"` + RoleClaim *string `json:"RoleClaim,omitempty"` + RolesSeparator *string `json:"RolesSeparator,omitempty"` + UniqueIdClaim *string `json:"UniqueIdClaim,omitempty"` + UsernameClaim *string `json:"UsernameClaim,omitempty"` + TokenLifetime *string `json:"TokenLifetime,omitempty"` +} + +// PackageManagerIdentityFederationConfigApplyConfiguration constructs a declarative configuration of the PackageManagerIdentityFederationConfig type for use with +// apply. +func PackageManagerIdentityFederationConfig() *PackageManagerIdentityFederationConfigApplyConfiguration { + return &PackageManagerIdentityFederationConfigApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithName(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Name = &value + return b +} + +// WithIssuer sets the Issuer field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Issuer field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithIssuer(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Issuer = &value + return b +} + +// WithLogging sets the Logging field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Logging field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithLogging(value bool) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Logging = &value + return b +} + +// WithAudience sets the Audience field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Audience field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithAudience(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Audience = &value + return b +} + +// WithSubject sets the Subject field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Subject field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithSubject(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Subject = &value + return b +} + +// WithAuthorizedParty sets the AuthorizedParty field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthorizedParty field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithAuthorizedParty(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.AuthorizedParty = &value + return b +} + +// WithScope sets the Scope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Scope field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithScope(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.Scope = &value + return b +} + +// WithCustomScope sets the CustomScope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CustomScope field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithCustomScope(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.CustomScope = &value + return b +} + +// WithNoAutoGroupsScope sets the NoAutoGroupsScope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NoAutoGroupsScope field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithNoAutoGroupsScope(value bool) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.NoAutoGroupsScope = &value + return b +} + +// WithGroupsClaim sets the GroupsClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupsClaim field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithGroupsClaim(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.GroupsClaim = &value + return b +} + +// WithGroupsSeparator sets the GroupsSeparator field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupsSeparator field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithGroupsSeparator(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.GroupsSeparator = &value + return b +} + +// WithRoleClaim sets the RoleClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleClaim field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithRoleClaim(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.RoleClaim = &value + return b +} + +// WithRolesSeparator sets the RolesSeparator field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RolesSeparator field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithRolesSeparator(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.RolesSeparator = &value + return b +} + +// WithUniqueIdClaim sets the UniqueIdClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UniqueIdClaim field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithUniqueIdClaim(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.UniqueIdClaim = &value + return b +} + +// WithUsernameClaim sets the UsernameClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UsernameClaim field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithUsernameClaim(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.UsernameClaim = &value + return b +} + +// WithTokenLifetime sets the TokenLifetime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TokenLifetime field is set to the value of the last call. +func (b *PackageManagerIdentityFederationConfigApplyConfiguration) WithTokenLifetime(value string) *PackageManagerIdentityFederationConfigApplyConfiguration { + b.TokenLifetime = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanageroidcconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanageroidcconfig.go new file mode 100644 index 00000000..aac85e68 --- /dev/null +++ b/client-go/applyconfiguration/core/v1beta1/packagemanageroidcconfig.go @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023-2026 Posit Software, PBC + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +// PackageManagerOIDCConfigApplyConfiguration represents a declarative configuration of the PackageManagerOIDCConfig type for use +// with apply. +type PackageManagerOIDCConfigApplyConfiguration struct { + ClientId *string `json:"ClientId,omitempty"` + ClientSecret *string `json:"ClientSecret,omitempty"` + ClientSecretFile *string `json:"ClientSecretFile,omitempty"` + Issuer *string `json:"Issuer,omitempty"` + RequireLogin *bool `json:"RequireLogin,omitempty"` + Logging *bool `json:"Logging,omitempty"` + Scope *string `json:"Scope,omitempty"` + CustomScope *string `json:"CustomScope,omitempty"` + NoAutoGroupsScope *bool `json:"NoAutoGroupsScope,omitempty"` + GroupsClaim *string `json:"GroupsClaim,omitempty"` + GroupsSeparator *string `json:"GroupsSeparator,omitempty"` + RoleClaim *string `json:"RoleClaim,omitempty"` + RolesSeparator *string `json:"RolesSeparator,omitempty"` + UniqueIdClaim *string `json:"UniqueIdClaim,omitempty"` + UsernameClaim *string `json:"UsernameClaim,omitempty"` + TokenLifetime *string `json:"TokenLifetime,omitempty"` + MaxAuthenticationAge *string `json:"MaxAuthenticationAge,omitempty"` + DisablePKCE *bool `json:"DisablePKCE,omitempty"` + EnableDevicePKCE *bool `json:"EnableDevicePKCE,omitempty"` +} + +// PackageManagerOIDCConfigApplyConfiguration constructs a declarative configuration of the PackageManagerOIDCConfig type for use with +// apply. +func PackageManagerOIDCConfig() *PackageManagerOIDCConfigApplyConfiguration { + return &PackageManagerOIDCConfigApplyConfiguration{} +} + +// WithClientId sets the ClientId field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientId field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithClientId(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.ClientId = &value + return b +} + +// WithClientSecret sets the ClientSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientSecret field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithClientSecret(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.ClientSecret = &value + return b +} + +// WithClientSecretFile sets the ClientSecretFile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientSecretFile field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithClientSecretFile(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.ClientSecretFile = &value + return b +} + +// WithIssuer sets the Issuer field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Issuer field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithIssuer(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.Issuer = &value + return b +} + +// WithRequireLogin sets the RequireLogin field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RequireLogin field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithRequireLogin(value bool) *PackageManagerOIDCConfigApplyConfiguration { + b.RequireLogin = &value + return b +} + +// WithLogging sets the Logging field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Logging field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithLogging(value bool) *PackageManagerOIDCConfigApplyConfiguration { + b.Logging = &value + return b +} + +// WithScope sets the Scope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Scope field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithScope(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.Scope = &value + return b +} + +// WithCustomScope sets the CustomScope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CustomScope field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithCustomScope(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.CustomScope = &value + return b +} + +// WithNoAutoGroupsScope sets the NoAutoGroupsScope field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NoAutoGroupsScope field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithNoAutoGroupsScope(value bool) *PackageManagerOIDCConfigApplyConfiguration { + b.NoAutoGroupsScope = &value + return b +} + +// WithGroupsClaim sets the GroupsClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupsClaim field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithGroupsClaim(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.GroupsClaim = &value + return b +} + +// WithGroupsSeparator sets the GroupsSeparator field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupsSeparator field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithGroupsSeparator(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.GroupsSeparator = &value + return b +} + +// WithRoleClaim sets the RoleClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleClaim field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithRoleClaim(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.RoleClaim = &value + return b +} + +// WithRolesSeparator sets the RolesSeparator field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RolesSeparator field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithRolesSeparator(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.RolesSeparator = &value + return b +} + +// WithUniqueIdClaim sets the UniqueIdClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UniqueIdClaim field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithUniqueIdClaim(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.UniqueIdClaim = &value + return b +} + +// WithUsernameClaim sets the UsernameClaim field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UsernameClaim field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithUsernameClaim(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.UsernameClaim = &value + return b +} + +// WithTokenLifetime sets the TokenLifetime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TokenLifetime field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithTokenLifetime(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.TokenLifetime = &value + return b +} + +// WithMaxAuthenticationAge sets the MaxAuthenticationAge field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxAuthenticationAge field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithMaxAuthenticationAge(value string) *PackageManagerOIDCConfigApplyConfiguration { + b.MaxAuthenticationAge = &value + return b +} + +// WithDisablePKCE sets the DisablePKCE field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DisablePKCE field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithDisablePKCE(value bool) *PackageManagerOIDCConfigApplyConfiguration { + b.DisablePKCE = &value + return b +} + +// WithEnableDevicePKCE sets the EnableDevicePKCE field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the EnableDevicePKCE field is set to the value of the last call. +func (b *PackageManagerOIDCConfigApplyConfiguration) WithEnableDevicePKCE(value bool) *PackageManagerOIDCConfigApplyConfiguration { + b.EnableDevicePKCE = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanagerspec.go b/client-go/applyconfiguration/core/v1beta1/packagemanagerspec.go index 742eb42b..4df5b1a1 100644 --- a/client-go/applyconfiguration/core/v1beta1/packagemanagerspec.go +++ b/client-go/applyconfiguration/core/v1beta1/packagemanagerspec.go @@ -37,6 +37,7 @@ type PackageManagerSpecApplyConfiguration struct { Replicas *int `json:"replicas,omitempty"` GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` + OIDCClientSecretKey *string `json:"oidcClientSecretKey,omitempty"` } // PackageManagerSpecApplyConfiguration constructs a declarative configuration of the PackageManagerSpec type for use with @@ -261,3 +262,11 @@ func (b *PackageManagerSpecApplyConfiguration) WithAzureFiles(value *AzureFilesC b.AzureFiles = value return b } + +// WithOIDCClientSecretKey sets the OIDCClientSecretKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OIDCClientSecretKey field is set to the value of the last call. +func (b *PackageManagerSpecApplyConfiguration) WithOIDCClientSecretKey(value string) *PackageManagerSpecApplyConfiguration { + b.OIDCClientSecretKey = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/sitespec.go b/client-go/applyconfiguration/core/v1beta1/sitespec.go index b13749bf..d7f52178 100644 --- a/client-go/applyconfiguration/core/v1beta1/sitespec.go +++ b/client-go/applyconfiguration/core/v1beta1/sitespec.go @@ -43,6 +43,8 @@ type SiteSpecApplyConfiguration struct { EFSEnabled *bool `json:"efsEnabled,omitempty"` VPCCIDR *string `json:"vpcCIDR,omitempty"` EnableFQDNHealthChecks *bool `json:"enableFqdnHealthChecks,omitempty"` + OIDCIssuerURL *string `json:"oidcIssuerUrl,omitempty"` + OIDCAudience *string `json:"oidcAudience,omitempty"` } // SiteSpecApplyConfiguration constructs a declarative configuration of the SiteSpec type for use with @@ -303,3 +305,19 @@ func (b *SiteSpecApplyConfiguration) WithEnableFQDNHealthChecks(value bool) *Sit b.EnableFQDNHealthChecks = &value return b } + +// WithOIDCIssuerURL sets the OIDCIssuerURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OIDCIssuerURL field is set to the value of the last call. +func (b *SiteSpecApplyConfiguration) WithOIDCIssuerURL(value string) *SiteSpecApplyConfiguration { + b.OIDCIssuerURL = &value + return b +} + +// WithOIDCAudience sets the OIDCAudience field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OIDCAudience field is set to the value of the last call. +func (b *SiteSpecApplyConfiguration) WithOIDCAudience(value string) *SiteSpecApplyConfiguration { + b.OIDCAudience = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchspec.go b/client-go/applyconfiguration/core/v1beta1/workbenchspec.go index e7ce73e5..8a9a498d 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchspec.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchspec.go @@ -47,6 +47,10 @@ type WorkbenchSpecApplyConfiguration struct { DsnSecret *string `json:"dsnSecret,omitempty"` ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` AuthLoginPageHtml *string `json:"authLoginPageHtml,omitempty"` + AuthenticatedRepos *bool `json:"authenticatedRepos,omitempty"` + PPMAuthImage *string `json:"ppmAuthImage,omitempty"` + PPMUrl *string `json:"ppmUrl,omitempty"` + PPMAuthAudience *string `json:"ppmAuthAudience,omitempty"` } // WorkbenchSpecApplyConfiguration constructs a declarative configuration of the WorkbenchSpec type for use with @@ -350,3 +354,35 @@ func (b *WorkbenchSpecApplyConfiguration) WithAuthLoginPageHtml(value string) *W b.AuthLoginPageHtml = &value return b } + +// WithAuthenticatedRepos sets the AuthenticatedRepos field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthenticatedRepos field is set to the value of the last call. +func (b *WorkbenchSpecApplyConfiguration) WithAuthenticatedRepos(value bool) *WorkbenchSpecApplyConfiguration { + b.AuthenticatedRepos = &value + return b +} + +// WithPPMAuthImage sets the PPMAuthImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthImage field is set to the value of the last call. +func (b *WorkbenchSpecApplyConfiguration) WithPPMAuthImage(value string) *WorkbenchSpecApplyConfiguration { + b.PPMAuthImage = &value + return b +} + +// WithPPMUrl sets the PPMUrl field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMUrl field is set to the value of the last call. +func (b *WorkbenchSpecApplyConfiguration) WithPPMUrl(value string) *WorkbenchSpecApplyConfiguration { + b.PPMUrl = &value + return b +} + +// WithPPMAuthAudience sets the PPMAuthAudience field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PPMAuthAudience field is set to the value of the last call. +func (b *WorkbenchSpecApplyConfiguration) WithPPMAuthAudience(value string) *WorkbenchSpecApplyConfiguration { + b.PPMAuthAudience = &value + return b +} diff --git a/client-go/applyconfiguration/utils.go b/client-go/applyconfiguration/utils.go index bba2e5a1..dbb7d774 100644 --- a/client-go/applyconfiguration/utils.go +++ b/client-go/applyconfiguration/utils.go @@ -125,6 +125,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &corev1beta1.InternalWorkbenchSpecApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManager"): return &corev1beta1.PackageManagerApplyConfiguration{} + case v1beta1.SchemeGroupVersion.WithKind("PackageManagerAuthenticationConfig"): + return &corev1beta1.PackageManagerAuthenticationConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerConfig"): return &corev1beta1.PackageManagerConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerCRANConfig"): @@ -137,8 +139,12 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &corev1beta1.PackageManagerGitConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerHttpConfig"): return &corev1beta1.PackageManagerHttpConfigApplyConfiguration{} + case v1beta1.SchemeGroupVersion.WithKind("PackageManagerIdentityFederationConfig"): + return &corev1beta1.PackageManagerIdentityFederationConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerMetricsConfig"): return &corev1beta1.PackageManagerMetricsConfigApplyConfiguration{} + case v1beta1.SchemeGroupVersion.WithKind("PackageManagerOIDCConfig"): + return &corev1beta1.PackageManagerOIDCConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerPostgresConfig"): return &corev1beta1.PackageManagerPostgresConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("PackageManagerReposConfig"): diff --git a/config/crd/bases/core.posit.team_connects.yaml b/config/crd/bases/core.posit.team_connects.yaml index 94495a6e..2f28ff90 100644 --- a/config/crd/bases/core.posit.team_connects.yaml +++ b/config/crd/bases/core.posit.team_connects.yaml @@ -178,6 +178,10 @@ spec: type: string type: array type: object + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Connect + type: boolean awsAccountId: description: AwsAccountId is the account Id for this AWS Account. It is used to create EKS-to-IAM annotations @@ -500,6 +504,18 @@ spec: type: object offHostExecution: type: boolean + ppmAuthAudience: + description: PPMAuthAudience is the audience claim for the projected + SA token used in PPM Identity Federation + type: string + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM auth + init/sidecar containers + type: string + ppmUrl: + description: PPMUrl specifies the PPM URL for authenticated repository + access + type: string registerOnFirstLogin: description: |- RegisterOnFirstLogin controls whether new users are automatically registered diff --git a/config/crd/bases/core.posit.team_packagemanagers.yaml b/config/crd/bases/core.posit.team_packagemanagers.yaml index 72619fa8..0ccfd9f4 100644 --- a/config/crd/bases/core.posit.team_packagemanagers.yaml +++ b/config/crd/bases/core.posit.team_packagemanagers.yaml @@ -75,6 +75,21 @@ spec: type: string config: properties: + Authentication: + properties: + APITokenAuth: + type: boolean + CookieSweepDuration: + type: string + DeviceAuthType: + type: string + Inactivity: + type: string + Lifetime: + type: string + NewReposAuthByDefault: + type: boolean + type: object CRAN: description: 'PackageManagerCRANConfig is deprecated TODO: deprecated! We will remove this soon!' @@ -107,6 +122,47 @@ spec: Enabled: type: boolean type: object + OpenIDConnect: + properties: + ClientId: + type: string + ClientSecret: + type: string + ClientSecretFile: + type: string + CustomScope: + type: string + DisablePKCE: + type: boolean + EnableDevicePKCE: + type: boolean + GroupsClaim: + type: string + GroupsSeparator: + type: string + Issuer: + type: string + Logging: + type: boolean + MaxAuthenticationAge: + type: string + NoAutoGroupsScope: + type: boolean + RequireLogin: + type: boolean + RoleClaim: + type: string + RolesSeparator: + type: string + Scope: + type: string + TokenLifetime: + type: string + UniqueIdClaim: + type: string + UsernameClaim: + type: string + type: object Postgres: properties: URL: @@ -160,6 +216,43 @@ spec: The value is appended verbatim after the generated config. gcfg parsing naturally handles conflicts: list values are combined, scalar values use the last occurrence. type: string + identityFederation: + items: + properties: + Audience: + type: string + AuthorizedParty: + type: string + CustomScope: + type: string + GroupsClaim: + type: string + GroupsSeparator: + type: string + Issuer: + type: string + Logging: + type: boolean + NoAutoGroupsScope: + type: boolean + RoleClaim: + type: string + RolesSeparator: + type: string + Scope: + type: string + Subject: + type: string + TokenLifetime: + type: string + UniqueIdClaim: + type: string + UsernameClaim: + type: string + name: + type: string + type: object + type: array type: object databaseConfig: properties: @@ -304,6 +397,11 @@ spec: additionalProperties: type: string type: object + oidcClientSecretKey: + description: |- + OIDCClientSecretKey is the key name in the vault for the OIDC client secret. + When set, the client secret will be mounted at /etc/rstudio-pm/oidc-client-secret + type: string replicas: type: integer secret: diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 647b764b..00efc4de 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -168,6 +168,10 @@ spec: type: string type: array type: object + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Connect + type: boolean baseDomain: description: |- BaseDomain overrides site.Spec.Domain for this product's URL construction. @@ -430,6 +434,10 @@ spec: additionalProperties: type: string type: object + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM + auth init/sidecar containers + type: string publicWarning: type: string registerOnFirstLogin: @@ -622,6 +630,15 @@ spec: maximum: 100 minimum: 0 type: integer + oidcAudience: + description: |- + OIDCAudience is the audience claim for the OIDC token used in Identity Federation. + For EKS this is typically "sts.amazonaws.com", for AKS it varies by configuration. + type: string + oidcIssuerUrl: + description: OIDCIssuerURL is the K8s cluster OIDC issuer URL (for + EKS/AKS Identity Federation) + type: string packageManager: description: PackageManager contains Posit Package Manager configuration properties: @@ -633,6 +650,59 @@ spec: description: AdditionalConfig allows appending arbitrary gcfg config content to the generated config. type: string + auth: + description: Auth configures OIDC authentication for Package Manager's + web UI + properties: + administratorRoleMapping: + items: + type: string + type: array + clientId: + type: string + disableGroupsClaim: + type: boolean + emailClaim: + type: string + groups: + type: boolean + groupsClaim: + type: string + issuer: + type: string + publisherRoleMapping: + items: + type: string + type: array + samlEmailAttribute: + type: string + samlFirstNameAttribute: + type: string + samlIdPAttributeProfile: + description: SAML-specific attribute mappings (mutually exclusive + with SamlIdPAttributeProfile) + type: string + samlLastNameAttribute: + type: string + samlMetadataUrl: + type: string + samlUsernameAttribute: + type: string + scopes: + items: + type: string + type: array + type: + type: string + uniqueIdClaim: + type: string + usernameClaim: + type: string + viewerRoleMapping: + items: + type: string + type: array + type: object azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -762,6 +832,10 @@ spec: additionalProperties: type: string type: object + oidcClientSecretKey: + description: OIDCClientSecretKey is the key in the vault for the + OIDC client secret + type: string replicas: type: integer s3Bucket: @@ -1056,6 +1130,10 @@ spec: authLoginPageHtml: description: Workbench Auth/Login Landing Page Customization HTML type: string + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Workbench + type: boolean baseDomain: description: |- BaseDomain overrides site.Spec.Domain for this product's URL construction. @@ -1461,6 +1539,10 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: object + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM + auth init/sidecar containers + type: string replicas: type: integer sessionInitContainerImageName: diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index d047931a..f4d9e9be 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -151,6 +151,10 @@ spec: Empty or whitespace-only content will be ignored. See: https://docs.posit.co/ide/server-pro/admin/authenticating_users/customizing_signin.html type: string + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Workbench + type: boolean awsAccountId: description: AwsAccountId is the account Id for this AWS Account. It is used to create EKS-to-IAM annotations @@ -691,6 +695,18 @@ spec: type: boolean parentUrl: type: string + ppmAuthAudience: + description: PPMAuthAudience is the audience claim for the projected + SA token used in PPM Identity Federation + type: string + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM auth + init/sidecar containers + type: string + ppmUrl: + description: PPMUrl specifies the PPM URL for authenticated repository + access + type: string replicas: type: integer secret: diff --git a/dist/chart/templates/crd/core.posit.team_connects.yaml b/dist/chart/templates/crd/core.posit.team_connects.yaml index 1a5664de..94bb31c1 100755 --- a/dist/chart/templates/crd/core.posit.team_connects.yaml +++ b/dist/chart/templates/crd/core.posit.team_connects.yaml @@ -199,6 +199,10 @@ spec: type: string type: array type: object + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Connect + type: boolean awsAccountId: description: AwsAccountId is the account Id for this AWS Account. It is used to create EKS-to-IAM annotations @@ -521,6 +525,18 @@ spec: type: object offHostExecution: type: boolean + ppmAuthAudience: + description: PPMAuthAudience is the audience claim for the projected + SA token used in PPM Identity Federation + type: string + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM auth + init/sidecar containers + type: string + ppmUrl: + description: PPMUrl specifies the PPM URL for authenticated repository + access + type: string registerOnFirstLogin: description: |- RegisterOnFirstLogin controls whether new users are automatically registered diff --git a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml index 323d55ae..082dbaae 100755 --- a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml +++ b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml @@ -96,6 +96,21 @@ spec: type: string config: properties: + Authentication: + properties: + APITokenAuth: + type: boolean + CookieSweepDuration: + type: string + DeviceAuthType: + type: string + Inactivity: + type: string + Lifetime: + type: string + NewReposAuthByDefault: + type: boolean + type: object CRAN: description: 'PackageManagerCRANConfig is deprecated TODO: deprecated! We will remove this soon!' @@ -128,6 +143,47 @@ spec: Enabled: type: boolean type: object + OpenIDConnect: + properties: + ClientId: + type: string + ClientSecret: + type: string + ClientSecretFile: + type: string + CustomScope: + type: string + DisablePKCE: + type: boolean + EnableDevicePKCE: + type: boolean + GroupsClaim: + type: string + GroupsSeparator: + type: string + Issuer: + type: string + Logging: + type: boolean + MaxAuthenticationAge: + type: string + NoAutoGroupsScope: + type: boolean + RequireLogin: + type: boolean + RoleClaim: + type: string + RolesSeparator: + type: string + Scope: + type: string + TokenLifetime: + type: string + UniqueIdClaim: + type: string + UsernameClaim: + type: string + type: object Postgres: properties: URL: @@ -181,6 +237,43 @@ spec: The value is appended verbatim after the generated config. gcfg parsing naturally handles conflicts: list values are combined, scalar values use the last occurrence. type: string + identityFederation: + items: + properties: + Audience: + type: string + AuthorizedParty: + type: string + CustomScope: + type: string + GroupsClaim: + type: string + GroupsSeparator: + type: string + Issuer: + type: string + Logging: + type: boolean + NoAutoGroupsScope: + type: boolean + RoleClaim: + type: string + RolesSeparator: + type: string + Scope: + type: string + Subject: + type: string + TokenLifetime: + type: string + UniqueIdClaim: + type: string + UsernameClaim: + type: string + name: + type: string + type: object + type: array type: object databaseConfig: properties: @@ -325,6 +418,11 @@ spec: additionalProperties: type: string type: object + oidcClientSecretKey: + description: |- + OIDCClientSecretKey is the key name in the vault for the OIDC client secret. + When set, the client secret will be mounted at /etc/rstudio-pm/oidc-client-secret + type: string replicas: type: integer secret: diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index e0bd60ea..1a0f7ba9 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -189,6 +189,10 @@ spec: type: string type: array type: object + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Connect + type: boolean baseDomain: description: |- BaseDomain overrides site.Spec.Domain for this product's URL construction. @@ -451,6 +455,10 @@ spec: additionalProperties: type: string type: object + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM + auth init/sidecar containers + type: string publicWarning: type: string registerOnFirstLogin: @@ -643,6 +651,15 @@ spec: maximum: 100 minimum: 0 type: integer + oidcAudience: + description: |- + OIDCAudience is the audience claim for the OIDC token used in Identity Federation. + For EKS this is typically "sts.amazonaws.com", for AKS it varies by configuration. + type: string + oidcIssuerUrl: + description: OIDCIssuerURL is the K8s cluster OIDC issuer URL (for + EKS/AKS Identity Federation) + type: string packageManager: description: PackageManager contains Posit Package Manager configuration properties: @@ -654,6 +671,59 @@ spec: description: AdditionalConfig allows appending arbitrary gcfg config content to the generated config. type: string + auth: + description: Auth configures OIDC authentication for Package Manager's + web UI + properties: + administratorRoleMapping: + items: + type: string + type: array + clientId: + type: string + disableGroupsClaim: + type: boolean + emailClaim: + type: string + groups: + type: boolean + groupsClaim: + type: string + issuer: + type: string + publisherRoleMapping: + items: + type: string + type: array + samlEmailAttribute: + type: string + samlFirstNameAttribute: + type: string + samlIdPAttributeProfile: + description: SAML-specific attribute mappings (mutually exclusive + with SamlIdPAttributeProfile) + type: string + samlLastNameAttribute: + type: string + samlMetadataUrl: + type: string + samlUsernameAttribute: + type: string + scopes: + items: + type: string + type: array + type: + type: string + uniqueIdClaim: + type: string + usernameClaim: + type: string + viewerRoleMapping: + items: + type: string + type: array + type: object azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -783,6 +853,10 @@ spec: additionalProperties: type: string type: object + oidcClientSecretKey: + description: OIDCClientSecretKey is the key in the vault for the + OIDC client secret + type: string replicas: type: integer s3Bucket: @@ -1077,6 +1151,10 @@ spec: authLoginPageHtml: description: Workbench Auth/Login Landing Page Customization HTML type: string + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Workbench + type: boolean baseDomain: description: |- BaseDomain overrides site.Spec.Domain for this product's URL construction. @@ -1482,6 +1560,10 @@ spec: x-kubernetes-preserve-unknown-fields: true type: object type: object + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM + auth init/sidecar containers + type: string replicas: type: integer sessionInitContainerImageName: diff --git a/dist/chart/templates/crd/core.posit.team_workbenches.yaml b/dist/chart/templates/crd/core.posit.team_workbenches.yaml index ff0ed92c..90e77d71 100755 --- a/dist/chart/templates/crd/core.posit.team_workbenches.yaml +++ b/dist/chart/templates/crd/core.posit.team_workbenches.yaml @@ -172,6 +172,10 @@ spec: Empty or whitespace-only content will be ignored. See: https://docs.posit.co/ide/server-pro/admin/authenticating_users/customizing_signin.html type: string + authenticatedRepos: + description: AuthenticatedRepos enables PPM authenticated repository + access for Workbench + type: boolean awsAccountId: description: AwsAccountId is the account Id for this AWS Account. It is used to create EKS-to-IAM annotations @@ -712,6 +716,18 @@ spec: type: boolean parentUrl: type: string + ppmAuthAudience: + description: PPMAuthAudience is the audience claim for the projected + SA token used in PPM Identity Federation + type: string + ppmAuthImage: + description: PPMAuthImage specifies the container image for PPM auth + init/sidecar containers + type: string + ppmUrl: + description: PPMUrl specifies the PPM URL for authenticated repository + access + type: string replicas: type: integer secret: diff --git a/internal/controller/core/connect.go b/internal/controller/core/connect.go index ec507619..d607ff83 100644 --- a/internal/controller/core/connect.go +++ b/internal/controller/core/connect.go @@ -586,6 +586,18 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. volumeFactory := c.CreateVolumeFactory(configCopy) secretVolumeFactory := c.CreateSecretVolumeFactory(configCopy) + // PPM authenticated repos support + ppmAuthVolumes, ppmAuthVolumeMounts, ppmAuthEnvVars, ppmAuthInitContainers, ppmAuthSidecarContainers := UnpackPPMAuthSetup( + SetupPPMAuth( + c.Spec.AuthenticatedRepos, + c.Spec.PPMUrl, + c.Spec.PPMAuthImage, + c.Spec.PPMAuthAudience, + c.SiteName(), + l, + ), + ) + var chronicleSeededEnv []corev1.EnvVar if c.Spec.ChronicleSidecarProductApiKeyEnabled { chronicleSeededEnv = []corev1.EnvVar{ @@ -630,8 +642,11 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. NodeSelector: c.Spec.NodeSelector, ImagePullSecrets: pullSecrets, ServiceAccountName: maybeServiceAccountName, - // TODO: go back to automounting service token... + // AutomountServiceAccountToken is false to avoid mounting the default SA token. + // This does NOT affect projected SA token volumes (used by PPM auth), which are + // explicit volume mounts independent of the automount setting. AutomountServiceAccountToken: ptr.To(false), + InitContainers: ppmAuthInitContainers, Containers: product.ConcatLists([]corev1.Container{ { Name: "connect", @@ -643,6 +658,7 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. volumeFactory.EnvVars(), secretVolumeFactory.EnvVars(), product.StringMapToEnvVars(c.Spec.AddEnv), + ppmAuthEnvVars, []corev1.EnvVar{ { Name: "LAUNCHER_INSTANCE_ID", @@ -670,6 +686,7 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. volumeFactory.VolumeMounts(), secretVolumeFactory.VolumeMounts(), c.TokenVolumeMounts(), + ppmAuthVolumeMounts, ), Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ @@ -702,6 +719,7 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. }, }, product.ChronicleSidecar(c, chronicleSeededEnv), + ppmAuthSidecarContainers, ), Affinity: &corev1.Affinity{ PodAntiAffinity: positcov1beta1.ComponentSpecPodAntiAffinity(c, req.Namespace), @@ -713,6 +731,7 @@ func (r *ConnectReconciler) ensureDeployedService(ctx context.Context, req ctrl. volumeFactory.Volumes(), secretVolumeFactory.Volumes(), c.TokenVolumes(), + ppmAuthVolumes, ), }, }, diff --git a/internal/controller/core/package_manager.go b/internal/controller/core/package_manager.go index af2fef6a..5ff81477 100644 --- a/internal/controller/core/package_manager.go +++ b/internal/controller/core/package_manager.go @@ -272,6 +272,11 @@ func (r *PackageManagerReconciler) ensureDeployedService(ctx context.Context, re "password": "pkg-db-password", } + // Add OIDC client secret to SecretProviderClass when configured + if pm.Spec.OIDCClientSecretKey != "" { + secretRefs["oidc-client-secret"] = pm.Spec.OIDCClientSecretKey + } + if targetSpc, err := product.GetSecretProviderClassForAllSecrets( pm, pm.SecretProviderClassName(), req.Namespace, pm.Spec.Secret.VaultName, diff --git a/internal/controller/core/ppm_auth.go b/internal/controller/core/ppm_auth.go new file mode 100644 index 00000000..e4fbca0d --- /dev/null +++ b/internal/controller/core/ppm_auth.go @@ -0,0 +1,308 @@ +package core + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/posit-dev/team-operator/api/product" + "github.com/rstudio/goex/ptr" +) + +const ( + ppmAuthScriptVolumeName = "ppm-auth-script" + ppmAuthTokenVolumeName = "ppm-sa-token" + ppmAuthNetrcVolumeName = "ppm-auth" + ppmAuthNetrcMountPath = "/mnt/ppm-auth" + ppmAuthNetrcPath = "/mnt/ppm-auth/netrc" + ppmAuthTokenMountPath = "/var/run/secrets/ppm-auth" + ppmAuthScriptMountPath = "/scripts" + // ppmAuthDefaultImage uses alpine:3 which has wget and sed via BusyBox + ppmAuthDefaultImage = "alpine:3" + ppmAuthDefaultRefresh = "3000" // 50 minutes (for 60 min token lifetime) +) + +// PPMAuthTokenExchangeScript returns the shell script content for the token exchange +// init container and sidecar. The script exchanges a K8s service account token for a +// PPM API token via RFC 8693 token exchange, then writes a netrc file and a curlrc +// file (so R's libcurl can also authenticate via --netrc-file). +func PPMAuthTokenExchangeScript() string { + return `#!/bin/sh +set -e + +SA_TOKEN_PATH="${SA_TOKEN_PATH:-/var/run/secrets/ppm-auth/token}" +NETRC_PATH="${NETRC_PATH:-/mnt/ppm-auth/netrc}" +CURLRC_PATH="${CURLRC_PATH:-/mnt/ppm-auth/.curlrc}" +PPM_URL="${PPM_URL}" +REFRESH_INTERVAL="${REFRESH_INTERVAL:-3000}" + +# extract_json_field extracts a string value from a JSON object using only +# shell builtins and sed. This avoids requiring jq in the container image. +# Usage: extract_json_field '{"key":"value"}' "key" +extract_json_field() { + echo "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' +} + +exchange_token() { + SA_TOKEN=$(cat "$SA_TOKEN_PATH") + + # Write POST data to a temp file to avoid exposing token in process args + POST_DATA_FILE=$(mktemp) + printf "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=%s&subject_token_type=urn:ietf:params:oauth:token-type:id_token" "$SA_TOKEN" > "$POST_DATA_FILE" + + # Use wget (BusyBox built-in) instead of curl for zero-dependency operation + RESPONSE=$(wget -qO- --header="Content-Type: application/x-www-form-urlencoded" \ + --post-file="$POST_DATA_FILE" \ + "${PPM_URL}/__api__/token") + + # Clean up temp file immediately + rm -f "$POST_DATA_FILE" + + PPM_TOKEN=$(extract_json_field "$RESPONSE" "access_token") + + if [ -z "$PPM_TOKEN" ] || [ "$PPM_TOKEN" = "null" ]; then + echo "ERROR: Failed to extract access_token from PPM response" >&2 + echo "Response length: ${#RESPONSE} bytes" >&2 + exit 1 + fi + + PPM_HOST=$(echo "$PPM_URL" | sed 's|https\?://||' | sed 's|/.*||') + + # Write netrc (atomic: write to temp, then rename) + TMPFILE=$(mktemp "${NETRC_PATH}.XXXXXX") + printf "machine %s\nlogin __token__\npassword %s\n" "$PPM_HOST" "$PPM_TOKEN" > "$TMPFILE" + mv "$TMPFILE" "$NETRC_PATH" + chmod 600 "$NETRC_PATH" + + # Write curlrc so R's libcurl uses the netrc file + printf -- "--netrc-file %s\n" "$NETRC_PATH" > "$CURLRC_PATH" + chmod 600 "$CURLRC_PATH" +} + +exchange_token + +if [ "${MODE}" = "sidecar" ]; then + while true; do + sleep "$REFRESH_INTERVAL" + exchange_token || echo "WARNING: token refresh failed, will retry" >&2 + done +fi +` +} + +// PPMAuthConfigMapName returns the name of the ConfigMap containing the token exchange script +func PPMAuthConfigMapName(siteName string) string { + return fmt.Sprintf("%s-ppm-auth-script", siteName) +} + +// PPMAuthInitContainer returns the init container spec for the PPM token exchange +func PPMAuthInitContainer(image, ppmURL string) corev1.Container { + if image == "" { + image = ppmAuthDefaultImage + } + return corev1.Container{ + Name: "ppm-auth-init", + Image: image, + Command: []string{"/scripts/token-exchange.sh"}, + Env: []corev1.EnvVar{ + {Name: "PPM_URL", Value: ppmURL}, + {Name: "MODE", Value: "init"}, + }, + VolumeMounts: ppmAuthContainerVolumeMounts(), + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To(int64(65534)), // nobody + AllowPrivilegeEscalation: ptr.To(false), + }, + } +} + +// PPMAuthSidecarContainer returns the sidecar container spec for the PPM token refresh +func PPMAuthSidecarContainer(image, ppmURL, refreshInterval string) corev1.Container { + if image == "" { + image = ppmAuthDefaultImage + } + if refreshInterval == "" { + refreshInterval = ppmAuthDefaultRefresh + } + return corev1.Container{ + Name: "ppm-auth-sidecar", + Image: image, + Command: []string{"/scripts/token-exchange.sh"}, + Env: []corev1.EnvVar{ + {Name: "PPM_URL", Value: ppmURL}, + {Name: "MODE", Value: "sidecar"}, + {Name: "REFRESH_INTERVAL", Value: refreshInterval}, + }, + VolumeMounts: ppmAuthContainerVolumeMounts(), + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To(int64(65534)), // nobody + AllowPrivilegeEscalation: ptr.To(false), + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("10m"), + corev1.ResourceMemory: resource.MustParse("16Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + }, + } +} + +// ppmAuthContainerVolumeMounts returns the volume mounts used by both init and sidecar containers +func ppmAuthContainerVolumeMounts() []corev1.VolumeMount { + return []corev1.VolumeMount{ + { + Name: ppmAuthTokenVolumeName, + MountPath: ppmAuthTokenMountPath, + ReadOnly: true, + }, + { + Name: ppmAuthNetrcVolumeName, + MountPath: ppmAuthNetrcMountPath, + }, + { + Name: ppmAuthScriptVolumeName, + MountPath: ppmAuthScriptMountPath, + ReadOnly: true, + }, + } +} + +// PPMAuthVolumes returns the volumes needed for PPM authenticated repo access: +// 1. Projected SA token volume (for K8s Identity Federation) +// 2. Shared emptyDir for netrc file +// 3. ConfigMap volume with the token exchange script +func PPMAuthVolumes(siteName, audience string) []corev1.Volume { + return []corev1.Volume{ + { + Name: ppmAuthTokenVolumeName, + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Path: "token", + ExpirationSeconds: ptr.To(int64(3600)), + Audience: audience, + }, + }, + }, + DefaultMode: ptr.To(product.MustParseOctal("0644")), + }, + }, + }, + { + Name: ppmAuthNetrcVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: ppmAuthScriptVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: PPMAuthConfigMapName(siteName), + }, + DefaultMode: ptr.To(product.MustParseOctal("0755")), + }, + }, + }, + } +} + +// PPMAuthVolumeMounts returns the volume mounts to add to the main product container +// for accessing the netrc file written by the init/sidecar containers +func PPMAuthVolumeMounts() []corev1.VolumeMount { + return []corev1.VolumeMount{ + { + Name: ppmAuthNetrcVolumeName, + MountPath: ppmAuthNetrcMountPath, + ReadOnly: true, + }, + } +} + +// PPMAuthEnvVars returns the environment variables to add to the main product container +// for authenticated PPM repo access: +// - NETRC: tells Python/pip where to find the netrc file +// - CURL_HOME: tells R's libcurl where to find the .curlrc file (which references the netrc) +func PPMAuthEnvVars() []corev1.EnvVar { + return []corev1.EnvVar{ + { + Name: "NETRC", + Value: ppmAuthNetrcPath, + }, + { + Name: "CURL_HOME", + Value: ppmAuthNetrcMountPath, + }, + } +} + +// SanitizePPMUrl strips any existing scheme from the URL and prepends https:// +// Returns an empty string if the input is empty. +func SanitizePPMUrl(rawUrl string) string { + host := strings.TrimPrefix(strings.TrimPrefix(rawUrl, "https://"), "http://") + if host == "" { + return "" + } + return fmt.Sprintf("https://%s", host) +} + +// PPMAuthSetup contains the volumes, mounts, env vars, and containers needed for PPM auth +type PPMAuthSetup struct { + Volumes []corev1.Volume + VolumeMounts []corev1.VolumeMount + EnvVars []corev1.EnvVar + InitContainers []corev1.Container + SidecarContainer []corev1.Container +} + +// SetupPPMAuth configures PPM authenticated repos for a product if enabled. +// Returns empty setup if AuthenticatedRepos is false or PPMUrl is empty. +// Logs a warning if AuthenticatedRepos is true but PPMUrl is empty. +func SetupPPMAuth(authenticatedRepos bool, ppmURL, ppmAuthImage, ppmAuthAudience, siteName string, logger interface { + Info(msg string, keysAndValues ...interface{}) +}) PPMAuthSetup { + if !authenticatedRepos { + return PPMAuthSetup{} + } + + sanitizedURL := SanitizePPMUrl(ppmURL) + if sanitizedURL == "" { + logger.Info("AuthenticatedRepos is enabled but PPMUrl is empty; skipping PPM auth setup") + return PPMAuthSetup{} + } + + return PPMAuthSetup{ + Volumes: PPMAuthVolumes(siteName, ppmAuthAudience), + VolumeMounts: PPMAuthVolumeMounts(), + EnvVars: PPMAuthEnvVars(), + InitContainers: []corev1.Container{ + PPMAuthInitContainer(ppmAuthImage, sanitizedURL), + }, + SidecarContainer: []corev1.Container{ + PPMAuthSidecarContainer(ppmAuthImage, sanitizedURL, ""), + }, + } +} + +// UnpackPPMAuthSetup unpacks a PPMAuthSetup struct into individual slices for use in pod specs. +// This helper reduces duplication in Connect and Workbench reconcilers. +func UnpackPPMAuthSetup(setup PPMAuthSetup) ( + volumes []corev1.Volume, + volumeMounts []corev1.VolumeMount, + envVars []corev1.EnvVar, + initContainers []corev1.Container, + sidecarContainers []corev1.Container, +) { + return setup.Volumes, setup.VolumeMounts, setup.EnvVars, setup.InitContainers, setup.SidecarContainer +} diff --git a/internal/controller/core/ppm_auth_test.go b/internal/controller/core/ppm_auth_test.go new file mode 100644 index 00000000..b6e9b6a9 --- /dev/null +++ b/internal/controller/core/ppm_auth_test.go @@ -0,0 +1,119 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPPMAuthTokenExchangeScript(t *testing.T) { + script := PPMAuthTokenExchangeScript() + require.Contains(t, script, "exchange_token") + require.Contains(t, script, "wget") + require.Contains(t, script, "grant_type=urn:ietf:params:oauth:grant-type:token-exchange") + require.Contains(t, script, "sidecar") + require.Contains(t, script, "netrc") + // Verify curlrc is written for R support + require.Contains(t, script, "CURLRC_PATH") + require.Contains(t, script, "--netrc-file") + require.Contains(t, script, ".curlrc") + // Verify null token validation + require.Contains(t, script, `[ "$PPM_TOKEN" = "null" ]`) + // Verify sidecar resilience + require.Contains(t, script, "WARNING: token refresh failed, will retry") + // Verify extract_json_field helper is present + require.Contains(t, script, "extract_json_field") +} + +func TestPPMAuthConfigMapName(t *testing.T) { + name := PPMAuthConfigMapName("mysite") + require.Equal(t, "mysite-ppm-auth-script", name) +} + +func TestPPMAuthInitContainer(t *testing.T) { + c := PPMAuthInitContainer("", "https://packagemanager.example.com") + require.Equal(t, "ppm-auth-init", c.Name) + require.Equal(t, "alpine:3", c.Image) + require.Len(t, c.Env, 2) + require.Equal(t, "PPM_URL", c.Env[0].Name) + require.Equal(t, "https://packagemanager.example.com", c.Env[0].Value) + require.Equal(t, "MODE", c.Env[1].Name) + require.Equal(t, "init", c.Env[1].Value) + require.Len(t, c.VolumeMounts, 3) + // Verify non-root security context (alpine:3 runs as root by default) + require.NotNil(t, c.SecurityContext) + require.NotNil(t, c.SecurityContext.RunAsUser) + require.Equal(t, int64(65534), *c.SecurityContext.RunAsUser) + require.True(t, *c.SecurityContext.RunAsNonRoot) +} + +func TestPPMAuthInitContainerCustomImage(t *testing.T) { + c := PPMAuthInitContainer("custom-image:v1", "https://ppm.example.com") + require.Equal(t, "custom-image:v1", c.Image) +} + +func TestPPMAuthSidecarContainer(t *testing.T) { + c := PPMAuthSidecarContainer("", "https://packagemanager.example.com", "") + require.Equal(t, "ppm-auth-sidecar", c.Name) + require.Equal(t, "alpine:3", c.Image) + require.Len(t, c.Env, 3) + require.Equal(t, "MODE", c.Env[1].Name) + require.Equal(t, "sidecar", c.Env[1].Value) + require.Equal(t, "REFRESH_INTERVAL", c.Env[2].Name) + require.Equal(t, "3000", c.Env[2].Value) + // Verify non-root security context (alpine:3 runs as root by default) + require.NotNil(t, c.SecurityContext) + require.NotNil(t, c.SecurityContext.RunAsUser) + require.Equal(t, int64(65534), *c.SecurityContext.RunAsUser) + require.True(t, *c.SecurityContext.RunAsNonRoot) +} + +func TestPPMAuthSidecarContainerCustomRefresh(t *testing.T) { + c := PPMAuthSidecarContainer("", "https://ppm.example.com", "1800") + require.Equal(t, "1800", c.Env[2].Value) +} + +func TestPPMAuthVolumes(t *testing.T) { + vols := PPMAuthVolumes("mysite", "sts.amazonaws.com") + require.Len(t, vols, 3) + + // Projected SA token volume + require.Equal(t, "ppm-sa-token", vols[0].Name) + require.NotNil(t, vols[0].Projected) + require.Len(t, vols[0].Projected.Sources, 1) + require.Equal(t, "sts.amazonaws.com", vols[0].Projected.Sources[0].ServiceAccountToken.Audience) + + // Shared emptyDir + require.Equal(t, "ppm-auth", vols[1].Name) + require.NotNil(t, vols[1].EmptyDir) + + // Script ConfigMap + require.Equal(t, "ppm-auth-script", vols[2].Name) + require.NotNil(t, vols[2].ConfigMap) + require.Equal(t, "mysite-ppm-auth-script", vols[2].ConfigMap.Name) +} + +func TestPPMAuthVolumeMounts(t *testing.T) { + mounts := PPMAuthVolumeMounts() + require.Len(t, mounts, 1) + require.Equal(t, "ppm-auth", mounts[0].Name) + require.Equal(t, "/mnt/ppm-auth", mounts[0].MountPath) + require.True(t, mounts[0].ReadOnly) +} + +func TestPPMAuthEnvVars(t *testing.T) { + envs := PPMAuthEnvVars() + require.Len(t, envs, 2) + require.Equal(t, "NETRC", envs[0].Name) + require.Equal(t, "/mnt/ppm-auth/netrc", envs[0].Value) + require.Equal(t, "CURL_HOME", envs[1].Name) + require.Equal(t, "/mnt/ppm-auth", envs[1].Value) +} + +func TestSanitizePPMUrl(t *testing.T) { + require.Equal(t, "https://ppm.example.com", SanitizePPMUrl("ppm.example.com")) + require.Equal(t, "https://ppm.example.com", SanitizePPMUrl("https://ppm.example.com")) + require.Equal(t, "https://ppm.example.com", SanitizePPMUrl("http://ppm.example.com")) + require.Equal(t, "", SanitizePPMUrl("")) + require.Equal(t, "https://ppm.example.com:8080/api", SanitizePPMUrl("ppm.example.com:8080/api")) +} diff --git a/internal/controller/core/site_controller.go b/internal/controller/core/site_controller.go index 441df348..4ee15338 100644 --- a/internal/controller/core/site_controller.go +++ b/internal/controller/core/site_controller.go @@ -308,6 +308,18 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques workbenchAdditionalVolumes := append([]product.VolumeSpec{}, additionalVolumes...) workbenchAdditionalVolumes = append(workbenchAdditionalVolumes, site.Spec.Workbench.AdditionalVolumes...) + // PPM AUTH CONFIGMAP + if site.Spec.Connect.AuthenticatedRepos || site.Spec.Workbench.AuthenticatedRepos { + if err := r.reconcilePPMAuthConfigMap(ctx, req, site); err != nil { + l.Error(err, "error reconciling PPM auth ConfigMap") + return ctrl.Result{}, err + } + } else { + if err := r.cleanupPPMAuthConfigMap(ctx, req, site); err != nil { + l.Error(err, "error cleaning up PPM auth ConfigMap") + } + } + // CONNECT if connectEnabled { // Connect is enabled - reconcile normally @@ -473,6 +485,52 @@ func (r *SiteReconciler) cleanupResources(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } +func (r *SiteReconciler) reconcilePPMAuthConfigMap(ctx context.Context, req ctrl.Request, site *positcov1beta1.Site) error { + l := r.GetLogger(ctx).WithValues("event", "reconcile-ppm-auth-configmap") + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: PPMAuthConfigMapName(site.Name), + Namespace: req.Namespace, + }, + } + + if _, err := internal.CreateOrUpdateResource(ctx, r.Client, r.Scheme, l, cm, site, func() error { + cm.Labels = map[string]string{ + positcov1beta1.ManagedByLabelKey: positcov1beta1.ManagedByLabelValue, + } + cm.Data = map[string]string{ + "token-exchange.sh": PPMAuthTokenExchangeScript(), + } + return nil + }); err != nil { + return err + } + + return nil +} + +func (r *SiteReconciler) cleanupPPMAuthConfigMap(ctx context.Context, req ctrl.Request, site *positcov1beta1.Site) error { + l := r.GetLogger(ctx).WithValues("event", "cleanup-ppm-auth-configmap") + + cm := &corev1.ConfigMap{} + key := client.ObjectKey{Name: PPMAuthConfigMapName(site.Name), Namespace: req.Namespace} + if err := r.Get(ctx, key, cm); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + + // Only delete if we own it (check ManagedByLabelKey) + if cm.Labels[positcov1beta1.ManagedByLabelKey] != positcov1beta1.ManagedByLabelValue { + l.Info("Skipping deletion of PPM auth ConfigMap - not managed by operator", "configmap", cm.Name, "label", cm.Labels[positcov1beta1.ManagedByLabelKey]) + return nil + } + + return r.Delete(ctx, cm) +} + // SetupWithManager sets up the controller with the Manager. func (r *SiteReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/core/site_controller_connect.go b/internal/controller/core/site_controller_connect.go index cd5ef5ad..f7024048 100644 --- a/internal/controller/core/site_controller_connect.go +++ b/internal/controller/core/site_controller_connect.go @@ -154,6 +154,10 @@ func (r *SiteReconciler) reconcileConnect( WorkloadSecret: site.Spec.WorkloadSecret, Debug: connectDebugLog, Replicas: product.PassDefaultReplicas(site.Spec.Connect.Replicas, 1), + AuthenticatedRepos: site.Spec.Connect.AuthenticatedRepos, + PPMAuthImage: site.Spec.Connect.PPMAuthImage, + PPMUrl: prefixDomain(site.Spec.PackageManager.DomainPrefix, getEffectiveBaseDomain(site.Spec.PackageManager.BaseDomain, site.Spec.Domain), v1beta1.SiteSubDomain), + PPMAuthAudience: site.Spec.OIDCAudience, }, } diff --git a/internal/controller/core/site_controller_package_manager.go b/internal/controller/core/site_controller_package_manager.go index bca0a645..ca3a523a 100644 --- a/internal/controller/core/site_controller_package_manager.go +++ b/internal/controller/core/site_controller_package_manager.go @@ -2,6 +2,7 @@ package core import ( "context" + "fmt" "github.com/posit-dev/team-operator/api/core/v1beta1" "github.com/posit-dev/team-operator/api/product" @@ -117,6 +118,59 @@ func (r *SiteReconciler) reconcilePackageManager( // Propagate additional config from Site to PackageManager pm.Spec.Config.AdditionalConfig = site.Spec.PackageManager.AdditionalConfig + // Propagate OIDC authentication configuration + if site.Spec.PackageManager.Auth != nil && site.Spec.PackageManager.Auth.Type == v1beta1.AuthTypeOidc { + pm.Spec.Config.OpenIDConnect = &v1beta1.PackageManagerOIDCConfig{ + ClientId: site.Spec.PackageManager.Auth.ClientId, + ClientSecretFile: "/etc/rstudio-pm/oidc-client-secret", + Issuer: site.Spec.PackageManager.Auth.Issuer, + RequireLogin: true, + Scope: "repos:read:*", + } + if site.Spec.PackageManager.Auth.GroupsClaim != "" { + pm.Spec.Config.OpenIDConnect.GroupsClaim = site.Spec.PackageManager.Auth.GroupsClaim + } + // Propagate the OIDC client secret key so the volume factory can mount it + pm.Spec.OIDCClientSecretKey = site.Spec.PackageManager.OIDCClientSecretKey + } + + // Auto-configure Identity Federation entries based on product flags + var idfEntries []v1beta1.PackageManagerIdentityFederationConfig + if site.Spec.OIDCIssuerURL != "" { + audience := site.Spec.OIDCAudience + if audience == "" { + audience = "sts.amazonaws.com" + l.Info("Using default OIDC audience for EKS clusters", "audience", audience, "reason", "OIDCAudience not specified in Site spec") + } + if site.Spec.Connect.AuthenticatedRepos { + idfEntries = append(idfEntries, v1beta1.PackageManagerIdentityFederationConfig{ + Name: "connect", + Issuer: site.Spec.OIDCIssuerURL, + Audience: audience, + Subject: fmt.Sprintf("system:serviceaccount:%s:%s-connect", req.Namespace, req.Name), + Scope: "repos:read:*", + UniqueIdClaim: "sub", + UsernameClaim: "sub", + }) + } + if site.Spec.Workbench.AuthenticatedRepos { + idfEntries = append(idfEntries, v1beta1.PackageManagerIdentityFederationConfig{ + Name: "workbench", + Issuer: site.Spec.OIDCIssuerURL, + Audience: audience, + Subject: fmt.Sprintf("system:serviceaccount:%s:%s-workbench", req.Namespace, req.Name), + Scope: "repos:read:*", + UniqueIdClaim: "sub", + UsernameClaim: "sub", + }) + } + } else if site.Spec.Connect.AuthenticatedRepos || site.Spec.Workbench.AuthenticatedRepos { + l.Info("AuthenticatedRepos is enabled but OIDCIssuerURL is empty; Identity Federation will not be configured") + } + if len(idfEntries) > 0 { + pm.Spec.Config.IdentityFederation = idfEntries + } + return nil }); err != nil { l.Error(err, "error creating package manager instance") diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index 428218ef..08370ea1 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -256,6 +256,10 @@ func (r *SiteReconciler) reconcileWorkbench( Secret: site.Spec.Secret, WorkloadSecret: site.Spec.WorkloadSecret, Replicas: product.PassDefaultReplicas(site.Spec.Workbench.Replicas, 1), + AuthenticatedRepos: site.Spec.Workbench.AuthenticatedRepos, + PPMAuthImage: site.Spec.Workbench.PPMAuthImage, + PPMUrl: prefixDomain(site.Spec.PackageManager.DomainPrefix, getEffectiveBaseDomain(site.Spec.PackageManager.BaseDomain, site.Spec.Domain), v1beta1.SiteSubDomain), + PPMAuthAudience: site.Spec.OIDCAudience, }, } // potentially enable experimental features diff --git a/internal/controller/core/workbench.go b/internal/controller/core/workbench.go index 721c9d5f..707b4ecd 100644 --- a/internal/controller/core/workbench.go +++ b/internal/controller/core/workbench.go @@ -745,6 +745,18 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr workbenchVolumeFactory := w.CreateVolumeFactory(configCopy) workbenchSecretVolumeFactory := w.CreateSecretVolumeFactory() + // PPM authenticated repos support + ppmAuthVolumes, ppmAuthVolumeMounts, ppmAuthEnvVars, ppmAuthInitContainers, ppmAuthSidecarContainers := UnpackPPMAuthSetup( + SetupPPMAuth( + w.Spec.AuthenticatedRepos, + w.Spec.PPMUrl, + w.Spec.PPMAuthImage, + w.Spec.PPMAuthAudience, + w.SiteName(), + l, + ), + ) + var chronicleSeededEnv []corev1.EnvVar if w.Spec.ChronicleSidecarProductApiKeyEnabled { chronicleSeededEnv = []corev1.EnvVar{ @@ -794,7 +806,10 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr ImagePullSecrets: pullSecrets, ServiceAccountName: maybeServiceAccountName, AutomountServiceAccountToken: ptr.To(true), - InitContainers: r.buildWorkbenchInitContainers(w), + InitContainers: product.ConcatLists( + r.buildWorkbenchInitContainers(w), + ppmAuthInitContainers, + ), Containers: product.ConcatLists( []corev1.Container{ { @@ -806,6 +821,7 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr workbenchSecretVolumeFactory.EnvVars(), chronicleFactory.EnvVars(), product.StringMapToEnvVars(w.Spec.AddEnv), + ppmAuthEnvVars, []corev1.EnvVar{ { Name: "LAUNCHER_INSTANCE_ID", @@ -835,6 +851,7 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr workbenchSecretVolumeFactory.VolumeMounts(), chronicleFactory.VolumeMounts(), r.buildLoadBalancerVolumeMounts(w), + ppmAuthVolumeMounts, ), Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ @@ -866,6 +883,7 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr }, }, chronicleFactory.Sidecars(), + ppmAuthSidecarContainers, ), Affinity: &corev1.Affinity{ PodAntiAffinity: positcov1beta1.ComponentSpecPodAntiAffinity(w, req.Namespace), @@ -879,6 +897,7 @@ func (r *WorkbenchReconciler) ensureDeployedService(ctx context.Context, req ctr workbenchSecretVolumeFactory.Volumes(), chronicleFactory.Volumes(), r.buildLoadBalancerVolumes(w), + ppmAuthVolumes, ), }, },