core/config: add kubernetes_service_account_token_file (#5322)

* core/config: add kubernetes_service_account_token_file

* fix loading of token file
This commit is contained in:
Caleb Doxsey 2024-10-10 14:53:45 -06:00 committed by Kenneth Jenkins
parent a68046e340
commit ee38346ca2
8 changed files with 739 additions and 712 deletions

View file

@ -298,7 +298,10 @@ func (e *Evaluator) evaluatePolicy(ctx context.Context, req *Request) (*PolicyRe
}
func (e *Evaluator) evaluateHeaders(ctx context.Context, req *Request) (*HeadersResponse, error) {
headersReq := NewHeadersRequestFromPolicy(req.Policy, req.HTTP)
headersReq, err := NewHeadersRequestFromPolicy(req.Policy, req.HTTP)
if err != nil {
return nil, err
}
headersReq.Session = req.Session
res, err := e.headersEvaluators.Evaluate(ctx, headersReq)
if err != nil {

View file

@ -30,21 +30,25 @@ type HeadersRequest struct {
}
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
func NewHeadersRequestFromPolicy(policy *config.Policy, http RequestHTTP) *HeadersRequest {
func NewHeadersRequestFromPolicy(policy *config.Policy, http RequestHTTP) (*HeadersRequest, error) {
input := new(HeadersRequest)
input.Issuer = http.Hostname
if policy != nil {
input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication
input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken
var err error
input.KubernetesServiceAccountToken, err = policy.GetKubernetesServiceAccountToken()
if err != nil {
return nil, err
}
for _, wu := range policy.To {
input.ToAudience = "https://" + wu.URL.Hostname()
}
input.ClientCertificate = http.ClientCertificate
input.SetRequestHeaders = policy.SetRequestHeaders
}
return input
return input, nil
}
// HeadersResponse is the output from the headers.rego script.

View file

@ -29,7 +29,7 @@ import (
)
func TestNewHeadersRequestFromPolicy(t *testing.T) {
req := NewHeadersRequestFromPolicy(&config.Policy{
req, _ := NewHeadersRequestFromPolicy(&config.Policy{
EnableGoogleCloudServerlessAuthentication: true,
From: "https://*.example.com",
To: config.WeightedURLs{
@ -54,7 +54,7 @@ func TestNewHeadersRequestFromPolicy(t *testing.T) {
}
func TestNewHeadersRequestFromPolicy_nil(t *testing.T) {
req := NewHeadersRequestFromPolicy(nil, RequestHTTP{Hostname: "from.example.com"})
req, _ := NewHeadersRequestFromPolicy(nil, RequestHTTP{Hostname: "from.example.com"})
assert.Equal(t, &HeadersRequest{
Issuer: "from.example.com",
}, req)

View file

@ -29,18 +29,19 @@ import (
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/pomerium/csrf"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/identity/oauth/apple"
"github.com/pomerium/protoutil/protorand"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"github.com/pomerium/csrf"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/identity/oauth/apple"
"github.com/pomerium/protoutil/protorand"
)
var cmpOptIgnoreUnexported = cmpopts.IgnoreUnexported(Options{}, Policy{})

View file

@ -233,52 +233,53 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
}
p := &Policy{
ID: pb.GetId(),
From: pb.GetFrom(),
AllowedUsers: pb.GetAllowedUsers(),
AllowAnyAuthenticatedUser: pb.GetAllowAnyAuthenticatedUser(),
AllowedDomains: pb.GetAllowedDomains(),
AllowedIDPClaims: identity.NewFlattenedClaimsFromPB(pb.GetAllowedIdpClaims()),
Prefix: pb.GetPrefix(),
Path: pb.GetPath(),
Regex: pb.GetRegex(),
PrefixRewrite: pb.GetPrefixRewrite(),
RegexRewritePattern: pb.GetRegexRewritePattern(),
RegexRewriteSubstitution: pb.GetRegexRewriteSubstitution(),
RegexPriorityOrder: pb.RegexPriorityOrder,
CORSAllowPreflight: pb.GetCorsAllowPreflight(),
AllowedUsers: pb.GetAllowedUsers(),
AllowPublicUnauthenticatedAccess: pb.GetAllowPublicUnauthenticatedAccess(),
AllowAnyAuthenticatedUser: pb.GetAllowAnyAuthenticatedUser(),
UpstreamTimeout: timeout,
IdleTimeout: idleTimeout,
AllowWebsockets: pb.GetAllowWebsockets(),
AllowSPDY: pb.GetAllowSpdy(),
TLSSkipVerify: pb.GetTlsSkipVerify(),
TLSServerName: pb.GetTlsServerName(),
TLSDownstreamServerName: pb.GetTlsDownstreamServerName(),
TLSUpstreamServerName: pb.GetTlsUpstreamServerName(),
TLSUpstreamAllowRenegotiation: pb.GetTlsUpstreamAllowRenegotiation(),
TLSCustomCA: pb.GetTlsCustomCa(),
TLSCustomCAFile: pb.GetTlsCustomCaFile(),
TLSClientCert: pb.GetTlsClientCert(),
TLSClientKey: pb.GetTlsClientKey(),
TLSClientCertFile: pb.GetTlsClientCertFile(),
TLSClientKeyFile: pb.GetTlsClientKeyFile(),
TLSDownstreamClientCA: pb.GetTlsDownstreamClientCa(),
TLSDownstreamClientCAFile: pb.GetTlsDownstreamClientCaFile(),
SetRequestHeaders: pb.GetSetRequestHeaders(),
RemoveRequestHeaders: pb.GetRemoveRequestHeaders(),
PreserveHostHeader: pb.GetPreserveHostHeader(),
HostRewrite: pb.GetHostRewrite(),
HostRewriteHeader: pb.GetHostRewriteHeader(),
HostPathRegexRewritePattern: pb.GetHostPathRegexRewritePattern(),
HostPathRegexRewriteSubstitution: pb.GetHostPathRegexRewriteSubstitution(),
PassIdentityHeaders: pb.PassIdentityHeaders,
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
SetResponseHeaders: pb.GetSetResponseHeaders(),
AllowWebsockets: pb.GetAllowWebsockets(),
CORSAllowPreflight: pb.GetCorsAllowPreflight(),
EnableGoogleCloudServerlessAuthentication: pb.GetEnableGoogleCloudServerlessAuthentication(),
IDPClientID: pb.GetIdpClientId(),
IDPClientSecret: pb.GetIdpClientSecret(),
ShowErrorDetails: pb.GetShowErrorDetails(),
From: pb.GetFrom(),
HostPathRegexRewritePattern: pb.GetHostPathRegexRewritePattern(),
HostPathRegexRewriteSubstitution: pb.GetHostPathRegexRewriteSubstitution(),
HostRewrite: pb.GetHostRewrite(),
HostRewriteHeader: pb.GetHostRewriteHeader(),
ID: pb.GetId(),
IdleTimeout: idleTimeout,
IDPClientID: pb.GetIdpClientId(),
IDPClientSecret: pb.GetIdpClientSecret(),
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(),
PassIdentityHeaders: pb.PassIdentityHeaders,
Path: pb.GetPath(),
Prefix: pb.GetPrefix(),
PrefixRewrite: pb.GetPrefixRewrite(),
PreserveHostHeader: pb.GetPreserveHostHeader(),
Regex: pb.GetRegex(),
RegexPriorityOrder: pb.RegexPriorityOrder,
RegexRewritePattern: pb.GetRegexRewritePattern(),
RegexRewriteSubstitution: pb.GetRegexRewriteSubstitution(),
RemoveRequestHeaders: pb.GetRemoveRequestHeaders(),
SetRequestHeaders: pb.GetSetRequestHeaders(),
SetResponseHeaders: pb.GetSetResponseHeaders(),
ShowErrorDetails: pb.GetShowErrorDetails(),
TLSClientCert: pb.GetTlsClientCert(),
TLSClientCertFile: pb.GetTlsClientCertFile(),
TLSClientKey: pb.GetTlsClientKey(),
TLSClientKeyFile: pb.GetTlsClientKeyFile(),
TLSCustomCA: pb.GetTlsCustomCa(),
TLSCustomCAFile: pb.GetTlsCustomCaFile(),
TLSDownstreamClientCA: pb.GetTlsDownstreamClientCa(),
TLSDownstreamClientCAFile: pb.GetTlsDownstreamClientCaFile(),
TLSDownstreamServerName: pb.GetTlsDownstreamServerName(),
TLSServerName: pb.GetTlsServerName(),
TLSSkipVerify: pb.GetTlsSkipVerify(),
TLSUpstreamAllowRenegotiation: pb.GetTlsUpstreamAllowRenegotiation(),
TLSUpstreamServerName: pb.GetTlsUpstreamServerName(),
UpstreamTimeout: timeout,
}
if pb.Redirect.IsSet() {
p.Redirect = &PolicyRedirect{
@ -372,49 +373,50 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
}
pb := &configpb.Route{
Name: fmt.Sprint(p.RouteID()),
Id: p.ID,
From: p.From,
AllowedUsers: p.AllowedUsers,
AllowAnyAuthenticatedUser: p.AllowAnyAuthenticatedUser,
AllowedDomains: p.AllowedDomains,
AllowedIdpClaims: p.AllowedIDPClaims.ToPB(),
Prefix: p.Prefix,
Path: p.Path,
Regex: p.Regex,
PrefixRewrite: p.PrefixRewrite,
RegexRewritePattern: p.RegexRewritePattern,
RegexRewriteSubstitution: p.RegexRewriteSubstitution,
RegexPriorityOrder: p.RegexPriorityOrder,
CorsAllowPreflight: p.CORSAllowPreflight,
AllowedUsers: p.AllowedUsers,
AllowPublicUnauthenticatedAccess: p.AllowPublicUnauthenticatedAccess,
AllowAnyAuthenticatedUser: p.AllowAnyAuthenticatedUser,
Timeout: timeout,
IdleTimeout: idleTimeout,
AllowWebsockets: p.AllowWebsockets,
AllowSpdy: p.AllowSPDY,
TlsSkipVerify: p.TLSSkipVerify,
TlsServerName: p.TLSServerName,
TlsUpstreamServerName: p.TLSUpstreamServerName,
TlsDownstreamServerName: p.TLSDownstreamServerName,
TlsCustomCa: p.TLSCustomCA,
TlsCustomCaFile: p.TLSCustomCAFile,
TlsClientCert: p.TLSClientCert,
TlsClientKey: p.TLSClientKey,
TlsClientCertFile: p.TLSClientCertFile,
TlsClientKeyFile: p.TLSClientKeyFile,
TlsDownstreamClientCa: p.TLSDownstreamClientCA,
TlsDownstreamClientCaFile: p.TLSDownstreamClientCAFile,
TlsUpstreamAllowRenegotiation: p.TLSUpstreamAllowRenegotiation,
SetRequestHeaders: p.SetRequestHeaders,
RemoveRequestHeaders: p.RemoveRequestHeaders,
PreserveHostHeader: p.PreserveHostHeader,
PassIdentityHeaders: p.PassIdentityHeaders,
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
AllowWebsockets: p.AllowWebsockets,
CorsAllowPreflight: p.CORSAllowPreflight,
EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication,
Policies: sps,
EnvoyOpts: p.EnvoyOpts,
SetResponseHeaders: p.SetResponseHeaders,
ShowErrorDetails: p.ShowErrorDetails,
EnvoyOpts: p.EnvoyOpts,
From: p.From,
Id: p.ID,
IdleTimeout: idleTimeout,
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile,
Name: fmt.Sprint(p.RouteID()),
PassIdentityHeaders: p.PassIdentityHeaders,
Path: p.Path,
Policies: sps,
Prefix: p.Prefix,
PrefixRewrite: p.PrefixRewrite,
PreserveHostHeader: p.PreserveHostHeader,
Regex: p.Regex,
RegexPriorityOrder: p.RegexPriorityOrder,
RegexRewritePattern: p.RegexRewritePattern,
RegexRewriteSubstitution: p.RegexRewriteSubstitution,
RemoveRequestHeaders: p.RemoveRequestHeaders,
SetRequestHeaders: p.SetRequestHeaders,
SetResponseHeaders: p.SetResponseHeaders,
ShowErrorDetails: p.ShowErrorDetails,
Timeout: timeout,
TlsClientCert: p.TLSClientCert,
TlsClientCertFile: p.TLSClientCertFile,
TlsClientKey: p.TLSClientKey,
TlsClientKeyFile: p.TLSClientKeyFile,
TlsCustomCa: p.TLSCustomCA,
TlsCustomCaFile: p.TLSCustomCAFile,
TlsDownstreamClientCa: p.TLSDownstreamClientCA,
TlsDownstreamClientCaFile: p.TLSDownstreamClientCAFile,
TlsDownstreamServerName: p.TLSDownstreamServerName,
TlsServerName: p.TLSServerName,
TlsSkipVerify: p.TLSSkipVerify,
TlsUpstreamAllowRenegotiation: p.TLSUpstreamAllowRenegotiation,
TlsUpstreamServerName: p.TLSUpstreamServerName,
}
if p.HostPathRegexRewritePattern != "" {
pb.HostPathRegexRewritePattern = proto.String(p.HostPathRegexRewritePattern)
@ -568,16 +570,8 @@ func (p *Policy) Validate() error {
p.TLSDownstreamClientCA = base64.StdEncoding.EncodeToString(bs)
}
if p.KubernetesServiceAccountTokenFile != "" {
if p.KubernetesServiceAccountToken != "" {
return fmt.Errorf("config: specified both `kubernetes_service_account_token_file` and `kubernetes_service_account_token`")
}
token, err := os.ReadFile(p.KubernetesServiceAccountTokenFile)
if err != nil {
return fmt.Errorf("config: failed to load kubernetes service account token: %w", err)
}
p.KubernetesServiceAccountToken = string(token)
if p.KubernetesServiceAccountTokenFile != "" && p.KubernetesServiceAccountToken != "" {
return fmt.Errorf("config: specified both `kubernetes_service_account_token_file` and `kubernetes_service_account_token`")
}
if p.PrefixRewrite != "" && p.RegexRewritePattern != "" {
@ -732,6 +726,20 @@ func (p *Policy) AllAllowedUsers() []string {
return aus
}
// GetKubernetesServiceAccountToken gets the kubernetes service account token from a file or from the config option.
func (p *Policy) GetKubernetesServiceAccountToken() (string, error) {
if p.KubernetesServiceAccountTokenFile != "" {
bs, err := os.ReadFile(p.KubernetesServiceAccountTokenFile)
return string(bs), err
}
if p.KubernetesServiceAccountToken != "" {
return p.KubernetesServiceAccountToken, nil
}
return "", nil
}
// GetPassIdentityHeaders gets the pass identity headers option. If not set in the policy, use the setting from the
// options. If not set in either, return false.
func (p *Policy) GetPassIdentityHeaders(options *Options) bool {

View file

@ -47,7 +47,6 @@ func Test_PolicyValidate(t *testing.T) {
{"bad key file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key-404.pem"}, true},
{"good tls server name", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), TLSServerName: "httpbin.corp.notatld"}, false},
{"good kube service account token file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, false},
{"bad kube service account token file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountTokenFile: "testdata/missing.token"}, true},
{"good kube service account token", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY"}, false},
{"bad kube service account token and file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY", KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, true},
{"TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "tcp://one.example.com:5000", "tcp://two.example.com:5000")}, false},

File diff suppressed because it is too large Load diff

View file

@ -38,7 +38,7 @@ message RouteDirectResponse {
string body = 2;
}
// Next ID: 63.
// Next ID: 65.
message Route {
string name = 1;
@ -103,6 +103,7 @@ message Route {
optional bool pass_identity_headers = 25;
string kubernetes_service_account_token = 26;
string kubernetes_service_account_token_file = 64;
bool enable_google_cloud_serverless_authentication = 42;
envoy.config.cluster.v3.Cluster envoy_opts = 36;
@ -121,9 +122,7 @@ message Route {
bool show_error_details = 59;
}
message PPLPolicy {
bytes raw = 1;
}
message PPLPolicy { bytes raw = 1; }
message Policy {
string id = 1;