Skip to content

Commit

Permalink
Add customized CA headers/metadata support to istio-agent (istio#55222)
Browse files Browse the repository at this point in the history
* Add customized CA headers/metadata support to istio-agent

* Add release note

* Linting
  • Loading branch information
Winbobob authored Feb 24, 2025
1 parent c902164 commit 499ead7
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 0 deletions.
23 changes: 23 additions & 0 deletions pilot/cmd/pilot-agent/options/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package options

import (
"fmt"
"os"
"strings"

meshconfig "istio.io/api/mesh/v1alpha1"
Expand All @@ -28,6 +29,9 @@ import (
"istio.io/istio/security/pkg/nodeagent/cafile"
)

// Similar with ISTIO_META_, which is used to customize the node metadata - this customizes extra CA header.
const caHeaderPrefix = "CA_HEADER_"

func NewSecurityOptions(proxyConfig *meshconfig.ProxyConfig, stsPort int, tokenManagerPlugin string) (*security.Options, error) {
o := &security.Options{
CAEndpoint: caEndpointEnv,
Expand Down Expand Up @@ -55,6 +59,7 @@ func NewSecurityOptions(proxyConfig *meshconfig.ProxyConfig, stsPort int, tokenM
CertChainFilePath: security.DefaultCertChainFilePath,
KeyFilePath: security.DefaultKeyFilePath,
RootCertFilePath: security.DefaultRootCertFilePath,
CAHeaders: map[string]string{},
}

o, err := SetupSecurityOptions(proxyConfig, o, jwtPolicy.Get(),
Expand All @@ -63,6 +68,8 @@ func NewSecurityOptions(proxyConfig *meshconfig.ProxyConfig, stsPort int, tokenM
return o, err
}

extractCAHeadersFromEnv(o)

return o, err
}

Expand Down Expand Up @@ -124,3 +131,19 @@ func SetupSecurityOptions(proxyConfig *meshconfig.ProxyConfig, secOpt *security.
}
return o, nil
}

// extractCAHeadersFromEnv extracts CA headers from environment variables.
func extractCAHeadersFromEnv(o *security.Options) {
envs := os.Environ()
for _, e := range envs {
if !strings.HasPrefix(e, caHeaderPrefix) {
continue
}

parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
continue
}
o.CAHeaders[parts[0][len(caHeaderPrefix):]] = parts[1]
}
}
78 changes: 78 additions & 0 deletions pilot/cmd/pilot-agent/options/security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,81 @@ func TestCheckGkeWorkloadCertificate(t *testing.T) {
}
}
}

func TestExtractCAHeadersFromEnv(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
expectedCAHeaders map[string]string
}{
{
name: "no CA headers",
envVars: map[string]string{
"RANDOM_KEY": "value",
},
expectedCAHeaders: map[string]string{},
},
{
name: "single CA header",
envVars: map[string]string{
"CA_HEADER_FOO": "foo",
},
expectedCAHeaders: map[string]string{
"FOO": "foo",
},
},
{
name: "multiple CA headers",
envVars: map[string]string{
"CA_HEADER_FOO": "foo",
"CA_HEADER_BAR": "bar",
},
expectedCAHeaders: map[string]string{
"FOO": "foo",
"BAR": "bar",
},
},
{
name: "mixed CA and non-CA headers",
envVars: map[string]string{
"CA_HEADER_FOO": "foo",
"XDS_HEADER_BAR": "bar",
"CA_HEADER_BAZ": "=baz",
},
expectedCAHeaders: map[string]string{
"FOO": "foo",
"BAZ": "=baz",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set environment variables
for k, v := range tt.envVars {
os.Setenv(k, v)
}
// Clean up environment variables after test
defer func() {
for k := range tt.envVars {
os.Unsetenv(k)
}
}()

o := &security.Options{
CAHeaders: map[string]string{},
}
extractCAHeadersFromEnv(o)

if len(o.CAHeaders) != len(tt.expectedCAHeaders) {
t.Errorf("expected %d CA headers, got %d", len(tt.expectedCAHeaders), len(o.CAHeaders))
}

for k, v := range tt.expectedCAHeaders {
if o.CAHeaders[k] != v {
t.Errorf("expected CA header %s to be %s, got %s", k, v, o.CAHeaders[k])
}
}
})
}
}
3 changes: 3 additions & 0 deletions pkg/security/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ type Options struct {
KeyFilePath string
// The path for an existing root certificate bundle
RootCertFilePath string

// Extra headers to add to the CA connection.
CAHeaders map[string]string
}

// Client interface defines the clients need to implement to talk to CA for CSR.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: release-notes/v2
kind: feature
area: traffic-management
issue:
- 55064
releaseNotes:
- |
**Added** an environment variable prefix `CA_HEADER_` (similar to `XDS_HEADER_``) that can be added to CA requests for different purposes, such as routing to appropriate external Istiods. Istio sidecar proxy, router, and waypoint now support this feature.
4 changes: 4 additions & 0 deletions security/pkg/nodeagent/caclient/providers/citadel/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func (c *CitadelClient) CSRSign(csrPEM []byte, certValidTTLInSec int64) (res []s
}()

ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("ClusterID", c.opts.ClusterID))
for k, v := range c.opts.CAHeaders {
ctx = metadata.AppendToOutgoingContext(ctx, k, v)
}

resp, err := c.client.CreateCertificate(ctx, req)
if err != nil {
return nil, fmt.Errorf("create certificate: %v", err)
Expand Down

0 comments on commit 499ead7

Please sign in to comment.