Browse Source

feat(github): add orgSecretVisibility field to GithubProvider (#6202)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Noah Perks Sloan 5 days ago
parent
commit
92d0e03130

+ 8 - 0
apis/externalsecrets/v1/secretstore_github_types.go

@@ -47,6 +47,14 @@ type GithubProvider struct {
 	// environment will be used to fetch secrets from a particular environment within a github repository
 	//+optional
 	Environment string `json:"environment,omitempty"`
+
+	// orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+	// Valid values are "all" or "private".
+	// When unset, new secrets are created with visibility "all" and existing secrets preserve
+	// whatever visibility they already have in GitHub.
+	//+optional
+	//+kubebuilder:validation:Enum=all;private
+	OrgSecretVisibility string `json:"orgSecretVisibility,omitempty"`
 }
 
 // GithubAppAuth defines authentication configuration using a GitHub App for accessing GitHub API.

+ 10 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2256,6 +2256,16 @@ spec:
                           that will be used to authenticate the client
                         format: int64
                         type: integer
+                      orgSecretVisibility:
+                        description: |-
+                          orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+                          Valid values are "all" or "private".
+                          When unset, new secrets are created with visibility "all" and existing secrets preserve
+                          whatever visibility they already have in GitHub.
+                        enum:
+                        - all
+                        - private
+                        type: string
                       organization:
                         description: organization will be used to fetch secrets from
                           the Github organization

+ 10 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2256,6 +2256,16 @@ spec:
                           that will be used to authenticate the client
                         format: int64
                         type: integer
+                      orgSecretVisibility:
+                        description: |-
+                          orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+                          Valid values are "all" or "private".
+                          When unset, new secrets are created with visibility "all" and existing secrets preserve
+                          whatever visibility they already have in GitHub.
+                        enum:
+                        - all
+                        - private
+                        type: string
                       organization:
                         description: organization will be used to fetch secrets from
                           the Github organization

+ 20 - 0
deploy/crds/bundle.yaml

@@ -4349,6 +4349,16 @@ spec:
                           description: installationID specifies the Github APP installation that will be used to authenticate the client
                           format: int64
                           type: integer
+                        orgSecretVisibility:
+                          description: |-
+                            orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+                            Valid values are "all" or "private".
+                            When unset, new secrets are created with visibility "all" and existing secrets preserve
+                            whatever visibility they already have in GitHub.
+                          enum:
+                            - all
+                            - private
+                          type: string
                         organization:
                           description: organization will be used to fetch secrets from the Github organization
                           type: string
@@ -16440,6 +16450,16 @@ spec:
                           description: installationID specifies the Github APP installation that will be used to authenticate the client
                           format: int64
                           type: integer
+                        orgSecretVisibility:
+                          description: |-
+                            orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+                            Valid values are "all" or "private".
+                            When unset, new secrets are created with visibility "all" and existing secrets preserve
+                            whatever visibility they already have in GitHub.
+                          enum:
+                            - all
+                            - private
+                          type: string
                         organization:
                           description: organization will be used to fetch secrets from the Github organization
                           type: string

+ 15 - 0
docs/api/spec.md

@@ -6045,6 +6045,21 @@ string
 <p>environment will be used to fetch secrets from a particular environment within a github repository</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>orgSecretVisibility</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+Valid values are &ldquo;all&rdquo; or &ldquo;private&rdquo;.
+When unset, new secrets are created with visibility &ldquo;all&rdquo; and existing secrets preserve
+whatever visibility they already have in GitHub.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.GitlabAuth">GitlabAuth

+ 17 - 2
providers/v1/github/client.go

@@ -123,10 +123,9 @@ func (g *Client) PushSecret(ctx context.Context, secret *corev1.Secret, remoteRe
 		return fmt.Errorf("box.SealAnonymous failed with error %w", err)
 	}
 	name := remoteRef.GetRemoteKey()
-	visibility := "all"
+	visibility := g.resolveOrgSecretVisibility(githubSecret)
 	if githubSecret != nil {
 		name = githubSecret.Name
-		visibility = githubSecret.Visibility
 	}
 	encryptedString := base64.StdEncoding.EncodeToString(encryptedBytes)
 	keyID := publicKey.GetKeyID()
@@ -144,6 +143,22 @@ func (g *Client) PushSecret(ctx context.Context, secret *corev1.Secret, remoteRe
 	return nil
 }
 
+// resolveOrgSecretVisibility returns the visibility to use when creating or updating an org secret.
+//
+// Rules:
+//   - If OrgSecretVisibility is set on the provider, that value is always used.
+//   - Otherwise, if the secret already exists in GitHub, its current visibility is preserved.
+//   - Otherwise (new secret, no provider override), visibility defaults to "all".
+func (g *Client) resolveOrgSecretVisibility(existing *github.Secret) string {
+	if g.provider != nil && g.provider.OrgSecretVisibility != "" {
+		return g.provider.OrgSecretVisibility
+	}
+	if existing != nil && existing.Visibility != "" {
+		return existing.Visibility
+	}
+	return "all"
+}
+
 // GetAllSecrets is not implemented as this provider is write-only.
 func (g *Client) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
 	return nil, fmt.Errorf(errWriteOnlyProvider)

+ 72 - 0
providers/v1/github/client_test.go

@@ -240,6 +240,78 @@ func TestPushSecret(t *testing.T) {
 	}
 }
 
+func TestResolveOrgSecretVisibility(t *testing.T) {
+	ptr := func(s string) *string { return &s }
+	tests := []struct {
+		name        string
+		nilProvider bool
+		providerViz string
+		existing    *github.Secret
+		want        string
+	}{
+		{
+			name:        "nil provider, no existing secret — defaults to all",
+			nilProvider: true,
+			existing:    nil,
+			want:        "all",
+		},
+		{
+			name:        "nil provider, existing secret has private — preserves private",
+			nilProvider: true,
+			existing:    &github.Secret{Visibility: *ptr("private")},
+			want:        "private",
+		},
+		{
+			name:        "provider unset, no existing secret — defaults to all",
+			providerViz: "",
+			existing:    nil,
+			want:        "all",
+		},
+		{
+			name:        "provider unset, existing secret has all — preserves all",
+			providerViz: "",
+			existing:    &github.Secret{Visibility: *ptr("all")},
+			want:        "all",
+		},
+		{
+			name:        "provider unset, existing secret has private — preserves private",
+			providerViz: "",
+			existing:    &github.Secret{Visibility: *ptr("private")},
+			want:        "private",
+		},
+		{
+			name:        "provider set to private, no existing secret",
+			providerViz: "private",
+			existing:    nil,
+			want:        "private",
+		},
+		{
+			name:        "provider set to private, existing secret has all — provider wins",
+			providerViz: "private",
+			existing:    &github.Secret{Visibility: *ptr("all")},
+			want:        "private",
+		},
+		{
+			name:        "provider set to all, existing secret has private — provider wins",
+			providerViz: "all",
+			existing:    &github.Secret{Visibility: *ptr("private")},
+			want:        "all",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := &Client{}
+			if !tt.nilProvider {
+				g.provider = &esv1.GithubProvider{
+					OrgSecretVisibility: tt.providerViz,
+				}
+			}
+			got := g.resolveOrgSecretVisibility(tt.existing)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
 // generateTestPrivateKey generates a PEM-encoded RSA private key for testing.
 func generateTestPrivateKey() (string, error) {
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)

+ 1 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -342,6 +342,7 @@ spec:
           namespace: string
       environment: string
       installationID: 1
+      orgSecretVisibility: "all" # "all", "private"
       organization: string
       repository: string
       uploadURL: string

+ 1 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -342,6 +342,7 @@ spec:
           namespace: string
       environment: string
       installationID: 1
+      orgSecretVisibility: "all" # "all", "private"
       organization: string
       repository: string
       uploadURL: string