mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
997 lines
40 KiB
Go
997 lines
40 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
|
|
"github.com/pomerium/pomerium/internal/hashutil"
|
|
"github.com/pomerium/pomerium/internal/log"
|
|
"github.com/pomerium/pomerium/internal/urlutil"
|
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
|
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
|
"github.com/pomerium/pomerium/pkg/identity"
|
|
)
|
|
|
|
// Policy contains route specific configuration and access settings.
|
|
type Policy struct {
|
|
ID string `mapstructure:"-" yaml:"-" json:"-"`
|
|
Name string `mapstructure:"-" yaml:"-" json:"-"`
|
|
Description string `mapstructure:"description" yaml:"description,omitempty" json:"description,omitempty"`
|
|
LogoURL string `mapstructure:"logo_url" yaml:"logo_url,omitempty" json:"logo_url,omitempty"`
|
|
|
|
From string `mapstructure:"from" yaml:"from"`
|
|
To WeightedURLs `mapstructure:"to" yaml:"to"`
|
|
// Redirect is used for a redirect action instead of `To`
|
|
Redirect *PolicyRedirect `mapstructure:"redirect" yaml:"redirect"`
|
|
Response *DirectResponse `mapstructure:"response" yaml:"response,omitempty" json:"response,omitempty"`
|
|
|
|
// LbWeights are optional load balancing weights applied to endpoints specified in To
|
|
// this field exists for compatibility with mapstructure
|
|
LbWeights []uint32 `mapstructure:"_to_weights,omitempty" json:"-" yaml:"-"`
|
|
|
|
// Identity related policy
|
|
AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"`
|
|
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
|
|
AllowedIDPClaims identity.FlattenedClaims `mapstructure:"allowed_idp_claims" yaml:"allowed_idp_claims,omitempty" json:"allowed_idp_claims,omitempty"`
|
|
|
|
// Additional route matching options
|
|
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
|
Path string `mapstructure:"path" yaml:"path,omitempty" json:"path,omitempty"`
|
|
Regex string `mapstructure:"regex" yaml:"regex,omitempty" json:"regex,omitempty"`
|
|
RegexPriorityOrder *int64 `mapstructure:"regex_priority_order" yaml:"regex_priority_order,omitempty" json:"regex_priority_order,omitempty"`
|
|
compiledRegex *regexp.Regexp
|
|
|
|
// Path Rewrite Options
|
|
PrefixRewrite string `mapstructure:"prefix_rewrite" yaml:"prefix_rewrite,omitempty" json:"prefix_rewrite,omitempty"`
|
|
RegexRewritePattern string `mapstructure:"regex_rewrite_pattern" yaml:"regex_rewrite_pattern,omitempty" json:"regex_rewrite_pattern,omitempty"`
|
|
RegexRewriteSubstitution string `mapstructure:"regex_rewrite_substitution" yaml:"regex_rewrite_substitution,omitempty" json:"regex_rewrite_substitution,omitempty"`
|
|
|
|
// Host Rewrite Options
|
|
HostRewrite string `mapstructure:"host_rewrite" yaml:"host_rewrite,omitempty" json:"host_rewrite,omitempty"`
|
|
HostRewriteHeader string `mapstructure:"host_rewrite_header" yaml:"host_rewrite_header,omitempty" json:"host_rewrite_header,omitempty"`
|
|
HostPathRegexRewritePattern string `mapstructure:"host_path_regex_rewrite_pattern" yaml:"host_path_regex_rewrite_pattern,omitempty" json:"host_path_regex_rewrite_pattern,omitempty"`
|
|
HostPathRegexRewriteSubstitution string `mapstructure:"host_path_regex_rewrite_substitution" yaml:"host_path_regex_rewrite_substitution,omitempty" json:"host_path_regex_rewrite_substitution,omitempty"`
|
|
|
|
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
|
|
CORSAllowPreflight bool `mapstructure:"cors_allow_preflight" yaml:"cors_allow_preflight,omitempty"`
|
|
|
|
// Allow any public request to access this route. **Bypasses authentication**
|
|
AllowPublicUnauthenticatedAccess bool `mapstructure:"allow_public_unauthenticated_access" yaml:"allow_public_unauthenticated_access,omitempty"`
|
|
|
|
// Allow any authenticated user
|
|
AllowAnyAuthenticatedUser bool `mapstructure:"allow_any_authenticated_user" yaml:"allow_any_authenticated_user,omitempty"`
|
|
|
|
// UpstreamTimeout is the route specific timeout. Must be less than the global
|
|
// timeout. If unset, route will fallback to the proxy's DefaultUpstreamTimeout.
|
|
UpstreamTimeout *time.Duration `mapstructure:"timeout" yaml:"timeout,omitempty"`
|
|
|
|
// IdleTimeout is distinct from UpstreamTimeout and defines period of time there may be no data over this connection
|
|
// value of zero completely disables this setting
|
|
// see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-idle-timeout
|
|
IdleTimeout *time.Duration `mapstructure:"idle_timeout" yaml:"idle_timeout,omitempty"`
|
|
|
|
// Enable proxying of websocket connections by removing the default timeout handler.
|
|
// Caution: Enabling this feature could result in abuse via DOS attacks.
|
|
AllowWebsockets bool `mapstructure:"allow_websockets" yaml:"allow_websockets,omitempty"`
|
|
|
|
// AllowSPDY enables proxying of SPDY upgrade requests
|
|
AllowSPDY bool `mapstructure:"allow_spdy" yaml:"allow_spdy,omitempty"`
|
|
|
|
// TLSSkipVerify controls whether a client verifies the server's certificate
|
|
// chain and host name.
|
|
// If TLSSkipVerify is true, TLS accepts any certificate presented by the
|
|
// server and any host name in that certificate.
|
|
// In this mode, TLS is susceptible to man-in-the-middle attacks.
|
|
// This should be used only for testing.
|
|
TLSSkipVerify bool `mapstructure:"tls_skip_verify" yaml:"tls_skip_verify,omitempty"`
|
|
|
|
// TLSServerName overrides the hostname in the `to` field. This is useful
|
|
// if your backend is an HTTPS server with a valid certificate, but you
|
|
// want to communicate to the backend with an internal hostname (e.g.
|
|
// Docker container name).
|
|
TLSServerName string `mapstructure:"tls_server_name" yaml:"tls_server_name,omitempty"`
|
|
TLSDownstreamServerName string `mapstructure:"tls_downstream_server_name" yaml:"tls_downstream_server_name,omitempty"`
|
|
TLSUpstreamServerName string `mapstructure:"tls_upstream_server_name" yaml:"tls_upstream_server_name,omitempty"`
|
|
|
|
// TLSCustomCA defines the root certificate to use with a given
|
|
// route when verifying server certificates.
|
|
TLSCustomCA string `mapstructure:"tls_custom_ca" yaml:"tls_custom_ca,omitempty"`
|
|
TLSCustomCAFile string `mapstructure:"tls_custom_ca_file" yaml:"tls_custom_ca_file,omitempty"`
|
|
|
|
// Contains the x.509 client certificate to present to the upstream host.
|
|
TLSClientCert string `mapstructure:"tls_client_cert" yaml:"tls_client_cert,omitempty"`
|
|
TLSClientKey string `mapstructure:"tls_client_key" yaml:"tls_client_key,omitempty"`
|
|
TLSClientCertFile string `mapstructure:"tls_client_cert_file" yaml:"tls_client_cert_file,omitempty"`
|
|
TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file,omitempty"`
|
|
ClientCertificate *tls.Certificate `yaml:",omitempty" hash:"ignore"`
|
|
|
|
// TLSDownstreamClientCA defines the root certificate to use with a given route to verify
|
|
// downstream client certificates (e.g. from a user's browser).
|
|
TLSDownstreamClientCA string `mapstructure:"tls_downstream_client_ca" yaml:"tls_downstream_client_ca,omitempty"`
|
|
TLSDownstreamClientCAFile string `mapstructure:"tls_downstream_client_ca_file" yaml:"tls_downstream_client_ca_file,omitempty"`
|
|
|
|
// TLSUpstreamAllowRenegotiation allows server-initiated TLS renegotiation.
|
|
TLSUpstreamAllowRenegotiation bool `mapstructure:"tls_upstream_allow_renegotiation" yaml:"allow_renegotiation,omitempty"`
|
|
|
|
// SetRequestHeaders adds a collection of headers to the upstream request
|
|
// in the form of key value pairs. Note bene, this will overwrite the
|
|
// value of any existing value of a given header key.
|
|
SetRequestHeaders map[string]string `mapstructure:"set_request_headers" yaml:"set_request_headers,omitempty"`
|
|
|
|
// RemoveRequestHeaders removes a collection of headers from an upstream request.
|
|
// Note that this has lower priority than `SetRequestHeaders`, if you specify `X-Custom-Header` in both
|
|
// `SetRequestHeaders` and `RemoveRequestHeaders`, then the header won't be removed.
|
|
RemoveRequestHeaders []string `mapstructure:"remove_request_headers" yaml:"remove_request_headers,omitempty"`
|
|
|
|
// PreserveHostHeader disables host header rewriting.
|
|
//
|
|
// This option only takes affect if the destination is a DNS name. If the destination is an IP address,
|
|
// use SetRequestHeaders to explicitly set the "Host" header.
|
|
//
|
|
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
|
|
PreserveHostHeader bool `mapstructure:"preserve_host_header" yaml:"preserve_host_header,omitempty"`
|
|
|
|
// PassIdentityHeaders controls whether to add a user's identity headers to the upstream request.
|
|
// These include:
|
|
//
|
|
// - X-Pomerium-Jwt-Assertion
|
|
// - X-Pomerium-Claim-*
|
|
//
|
|
PassIdentityHeaders *bool `mapstructure:"pass_identity_headers" yaml:"pass_identity_headers,omitempty"`
|
|
|
|
// KubernetesServiceAccountToken is the kubernetes token to use for upstream requests.
|
|
KubernetesServiceAccountToken string `mapstructure:"kubernetes_service_account_token" yaml:"kubernetes_service_account_token,omitempty"`
|
|
// KubernetesServiceAccountTokenFile contains the kubernetes token to use for upstream requests.
|
|
KubernetesServiceAccountTokenFile string `mapstructure:"kubernetes_service_account_token_file" yaml:"kubernetes_service_account_token_file,omitempty"`
|
|
|
|
// EnableGoogleCloudServerlessAuthentication adds "Authorization: Bearer ID_TOKEN" headers
|
|
// to upstream requests.
|
|
EnableGoogleCloudServerlessAuthentication bool `mapstructure:"enable_google_cloud_serverless_authentication" yaml:"enable_google_cloud_serverless_authentication,omitempty"`
|
|
|
|
// JWTIssuerFormat controls the format of the 'iss' claim in JWTs passed to upstream services by this route.
|
|
// Possible values:
|
|
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
|
|
// - "uri": Issuer strings will be a complete URI, including the scheme and ending with a trailing slash.
|
|
JWTIssuerFormat JWTIssuerFormat `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
|
|
// BearerTokenFormat indicates how authorization bearer tokens are interepreted. Possible values:
|
|
// - "default": Only Bearer tokens prefixed with Pomerium- will be interpreted by Pomerium
|
|
// - "idp_access_token": The Bearer token will be interpreted as an IdP access token.
|
|
// - "idp_identity_token": The Bearer token will be interpreted as an IdP identity token.
|
|
// When unset the global option will be used.
|
|
BearerTokenFormat *BearerTokenFormat `mapstructure:"bearer_token_format" yaml:"bearer_token_format,omitempty"`
|
|
|
|
// Allowlist of group names/IDs to include in the Pomerium JWT.
|
|
// This expands on any global allowlist set in the main Options.
|
|
JWTGroupsFilter JWTGroupsFilter
|
|
|
|
SubPolicies []SubPolicy `mapstructure:"sub_policies" yaml:"sub_policies,omitempty" json:"sub_policies,omitempty"`
|
|
|
|
EnvoyOpts *envoy_config_cluster_v3.Cluster `mapstructure:"_envoy_opts" yaml:"-" json:"-"`
|
|
|
|
// RewriteResponseHeaders rewrites response headers. This can be used to change the Location header.
|
|
RewriteResponseHeaders []RewriteHeader `mapstructure:"rewrite_response_headers" yaml:"rewrite_response_headers,omitempty" json:"rewrite_response_headers,omitempty"`
|
|
|
|
// SetResponseHeaders sets response headers.
|
|
SetResponseHeaders map[string]string `mapstructure:"set_response_headers" yaml:"set_response_headers,omitempty"`
|
|
|
|
// IDPClientID is the client id used for the identity provider.
|
|
IDPClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
|
|
// IDPClientSecret is the client secret used for the identity provider.
|
|
IDPClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
|
|
// IDPAccessTokenAllowedAudiences are the allowed audiences for idp access token validation.
|
|
IDPAccessTokenAllowedAudiences *[]string `mapstructure:"idp_access_token_allowed_audiences" yaml:"idp_access_token_allowed_audiences,omitempty"`
|
|
|
|
// ShowErrorDetails indicates whether or not additional error details should be displayed.
|
|
ShowErrorDetails bool `mapstructure:"show_error_details" yaml:"show_error_details" json:"show_error_details"`
|
|
|
|
Policy *PPLPolicy `mapstructure:"policy" yaml:"policy,omitempty" json:"policy,omitempty"`
|
|
|
|
DependsOn []string `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
|
|
|
|
// MCP is an experimental support for Model Context Protocol upstreams
|
|
MCP *MCP `mapstructure:"mcp" yaml:"mcp,omitempty" json:"mcp,omitempty"`
|
|
}
|
|
|
|
// MCP is an experimental support for Model Context Protocol upstreams configuration
|
|
type MCP struct{}
|
|
|
|
// RewriteHeader is a policy configuration option to rewrite an HTTP header.
|
|
type RewriteHeader struct {
|
|
Header string `mapstructure:"header" yaml:"header" json:"header"`
|
|
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
|
Value string `mapstructure:"value" yaml:"value,omitempty" json:"value,omitempty"`
|
|
}
|
|
|
|
// A SubPolicy is a protobuf Policy within a protobuf Route.
|
|
type SubPolicy struct {
|
|
ID string `mapstructure:"id" yaml:"id" json:"id"`
|
|
Name string `mapstructure:"name" yaml:"name" json:"name"`
|
|
AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"`
|
|
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
|
|
AllowedIDPClaims identity.FlattenedClaims `mapstructure:"allowed_idp_claims" yaml:"allowed_idp_claims,omitempty" json:"allowed_idp_claims,omitempty"`
|
|
Rego []string `mapstructure:"rego" yaml:"rego" json:"rego,omitempty"`
|
|
SourcePPL string `mapstructure:"source_ppl" yaml:"source_ppl,omitempty" json:"source_ppl,omitempty"`
|
|
|
|
// Explanation is the explanation for why a policy failed.
|
|
Explanation string `mapstructure:"explanation" yaml:"explanation" json:"explanation,omitempty"`
|
|
// Remediation are the steps a user needs to take to gain access.
|
|
Remediation string `mapstructure:"remediation" yaml:"remediation" json:"remediation,omitempty"`
|
|
}
|
|
|
|
// PolicyRedirect is a route redirect action.
|
|
type PolicyRedirect struct {
|
|
HTTPSRedirect *bool `mapstructure:"https_redirect" yaml:"https_redirect,omitempty" json:"https_redirect,omitempty"`
|
|
SchemeRedirect *string `mapstructure:"scheme_redirect" yaml:"scheme_redirect,omitempty" json:"scheme_redirect,omitempty"`
|
|
HostRedirect *string `mapstructure:"host_redirect" yaml:"host_redirect,omitempty" json:"host_redirect,omitempty"`
|
|
PortRedirect *uint32 `mapstructure:"port_redirect" yaml:"port_redirect,omitempty" json:"port_redirect,omitempty"`
|
|
PathRedirect *string `mapstructure:"path_redirect" yaml:"path_redirect,omitempty" json:"path_redirect,omitempty"`
|
|
PrefixRewrite *string `mapstructure:"prefix_rewrite" yaml:"prefix_rewrite,omitempty" json:"prefix_rewrite,omitempty"`
|
|
ResponseCode *int32 `mapstructure:"response_code" yaml:"response_code,omitempty" json:"response_code,omitempty"`
|
|
StripQuery *bool `mapstructure:"strip_query" yaml:"strip_query,omitempty" json:"strip_query,omitempty"`
|
|
}
|
|
|
|
func (r *PolicyRedirect) validate() error {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
if _, err := r.GetEnvoyResponseCode(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetEnvoyResponseCode returns the ResponseCode as the corresponding Envoy enum value.
|
|
func (r *PolicyRedirect) GetEnvoyResponseCode() (envoy_config_route_v3.RedirectAction_RedirectResponseCode, error) {
|
|
if r == nil || r.ResponseCode == nil {
|
|
return envoy_config_route_v3.RedirectAction_RedirectResponseCode(0), nil
|
|
}
|
|
switch code := *r.ResponseCode; code {
|
|
case http.StatusMovedPermanently:
|
|
return envoy_config_route_v3.RedirectAction_MOVED_PERMANENTLY, nil
|
|
case http.StatusFound:
|
|
return envoy_config_route_v3.RedirectAction_FOUND, nil
|
|
case http.StatusSeeOther:
|
|
return envoy_config_route_v3.RedirectAction_SEE_OTHER, nil
|
|
case http.StatusTemporaryRedirect:
|
|
return envoy_config_route_v3.RedirectAction_TEMPORARY_REDIRECT, nil
|
|
case http.StatusPermanentRedirect:
|
|
return envoy_config_route_v3.RedirectAction_PERMANENT_REDIRECT, nil
|
|
default:
|
|
return 0, fmt.Errorf("unsupported redirect response code %d (supported values: 301, 302, 303, 307, 308)", code)
|
|
}
|
|
}
|
|
|
|
// A DirectResponse is the response to an HTTP request.
|
|
type DirectResponse struct {
|
|
Status int `mapstructure:"status" yaml:"status,omitempty" json:"status,omitempty"`
|
|
Body string `mapstructure:"body" yaml:"body,omitempty" json:"body,omitempty"`
|
|
}
|
|
|
|
// NewPolicyFromProto creates a new Policy from a protobuf policy config route.
|
|
func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
|
|
var timeout *time.Duration
|
|
if pb.GetTimeout() != nil {
|
|
t := pb.GetTimeout().AsDuration()
|
|
timeout = &t
|
|
}
|
|
var idleTimeout *time.Duration
|
|
if pb.GetIdleTimeout() != nil {
|
|
t := pb.GetIdleTimeout().AsDuration()
|
|
idleTimeout = &t
|
|
}
|
|
|
|
p := &Policy{
|
|
AllowAnyAuthenticatedUser: pb.GetAllowAnyAuthenticatedUser(),
|
|
AllowedDomains: pb.GetAllowedDomains(),
|
|
AllowedIDPClaims: identity.NewFlattenedClaimsFromPB(pb.GetAllowedIdpClaims()),
|
|
AllowedUsers: pb.GetAllowedUsers(),
|
|
AllowPublicUnauthenticatedAccess: pb.GetAllowPublicUnauthenticatedAccess(),
|
|
AllowSPDY: pb.GetAllowSpdy(),
|
|
AllowWebsockets: pb.GetAllowWebsockets(),
|
|
CORSAllowPreflight: pb.GetCorsAllowPreflight(),
|
|
Description: pb.GetDescription(),
|
|
DependsOn: pb.GetDependsOn(),
|
|
EnableGoogleCloudServerlessAuthentication: pb.GetEnableGoogleCloudServerlessAuthentication(),
|
|
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(),
|
|
JWTGroupsFilter: NewJWTGroupsFilter(pb.JwtGroupsFilter),
|
|
JWTIssuerFormat: JWTIssuerFormatFromPB(pb.JwtIssuerFormat),
|
|
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
|
|
KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(),
|
|
LogoURL: pb.GetLogoUrl(),
|
|
MCP: MCPFromPB(pb.GetMcp()),
|
|
Name: pb.GetName(),
|
|
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.IdpAccessTokenAllowedAudiences != nil {
|
|
values := slices.Clone(pb.IdpAccessTokenAllowedAudiences.Values)
|
|
p.IDPAccessTokenAllowedAudiences = &values
|
|
} else {
|
|
p.IDPAccessTokenAllowedAudiences = nil
|
|
}
|
|
if pb.Redirect.IsSet() {
|
|
p.Redirect = &PolicyRedirect{
|
|
HTTPSRedirect: pb.Redirect.HttpsRedirect,
|
|
SchemeRedirect: pb.Redirect.SchemeRedirect,
|
|
HostRedirect: pb.Redirect.HostRedirect,
|
|
PortRedirect: pb.Redirect.PortRedirect,
|
|
PathRedirect: pb.Redirect.PathRedirect,
|
|
PrefixRewrite: pb.Redirect.PrefixRewrite,
|
|
ResponseCode: pb.Redirect.ResponseCode,
|
|
StripQuery: pb.Redirect.StripQuery,
|
|
}
|
|
} else if pb.Response != nil {
|
|
p.Response = &DirectResponse{
|
|
Status: int(pb.Response.GetStatus()),
|
|
Body: pb.Response.GetBody(),
|
|
}
|
|
} else {
|
|
p.To = make(WeightedURLs, len(pb.To))
|
|
for i, u := range pb.To {
|
|
u, err := urlutil.ParseAndValidateURL(u)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w := WeightedURL{
|
|
URL: *u,
|
|
}
|
|
if len(pb.LoadBalancingWeights) == len(pb.To) {
|
|
w.LbWeight = pb.LoadBalancingWeights[i]
|
|
}
|
|
p.To[i] = w
|
|
}
|
|
}
|
|
|
|
p.EnvoyOpts = pb.EnvoyOpts
|
|
if p.EnvoyOpts == nil {
|
|
p.EnvoyOpts = new(envoy_config_cluster_v3.Cluster)
|
|
}
|
|
if pb.Name != "" && p.EnvoyOpts.Name == "" {
|
|
p.EnvoyOpts.Name = pb.Name
|
|
}
|
|
|
|
p.BearerTokenFormat = BearerTokenFormatFromPB(pb.BearerTokenFormat)
|
|
|
|
for _, rwh := range pb.RewriteResponseHeaders {
|
|
p.RewriteResponseHeaders = append(p.RewriteResponseHeaders, RewriteHeader{
|
|
Header: rwh.GetHeader(),
|
|
Prefix: rwh.GetPrefix(),
|
|
Value: rwh.GetValue(),
|
|
})
|
|
}
|
|
|
|
for _, sp := range pb.GetPolicies() {
|
|
p.SubPolicies = append(p.SubPolicies, SubPolicy{
|
|
ID: sp.GetId(),
|
|
Name: sp.GetName(),
|
|
AllowedUsers: sp.GetAllowedUsers(),
|
|
AllowedDomains: sp.GetAllowedDomains(),
|
|
AllowedIDPClaims: identity.NewFlattenedClaimsFromPB(sp.GetAllowedIdpClaims()),
|
|
Rego: sp.GetRego(),
|
|
SourcePPL: sp.GetSourcePpl(),
|
|
|
|
Explanation: sp.GetExplanation(),
|
|
Remediation: sp.GetRemediation(),
|
|
})
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// ToProto converts the policy to a protobuf type.
|
|
func (p *Policy) ToProto() (*configpb.Route, error) {
|
|
var timeout *durationpb.Duration
|
|
if p.UpstreamTimeout == nil {
|
|
timeout = durationpb.New(defaultOptions.DefaultUpstreamTimeout)
|
|
} else {
|
|
timeout = durationpb.New(*p.UpstreamTimeout)
|
|
}
|
|
var idleTimeout *durationpb.Duration
|
|
if p.IdleTimeout != nil {
|
|
idleTimeout = durationpb.New(*p.IdleTimeout)
|
|
}
|
|
sps := make([]*configpb.Policy, 0, len(p.SubPolicies))
|
|
for _, sp := range p.SubPolicies {
|
|
p := &configpb.Policy{
|
|
Id: sp.ID,
|
|
Name: sp.Name,
|
|
AllowedUsers: sp.AllowedUsers,
|
|
AllowedDomains: sp.AllowedDomains,
|
|
AllowedIdpClaims: sp.AllowedIDPClaims.ToPB(),
|
|
Explanation: sp.Explanation,
|
|
Remediation: sp.Remediation,
|
|
Rego: sp.Rego,
|
|
}
|
|
if sp.SourcePPL != "" {
|
|
p.SourcePpl = proto.String(sp.SourcePPL)
|
|
}
|
|
sps = append(sps, p)
|
|
}
|
|
|
|
pb := &configpb.Route{
|
|
AllowAnyAuthenticatedUser: p.AllowAnyAuthenticatedUser,
|
|
AllowedDomains: p.AllowedDomains,
|
|
AllowedIdpClaims: p.AllowedIDPClaims.ToPB(),
|
|
AllowedUsers: p.AllowedUsers,
|
|
AllowPublicUnauthenticatedAccess: p.AllowPublicUnauthenticatedAccess,
|
|
AllowSpdy: p.AllowSPDY,
|
|
AllowWebsockets: p.AllowWebsockets,
|
|
CorsAllowPreflight: p.CORSAllowPreflight,
|
|
Description: p.Description,
|
|
DependsOn: p.DependsOn,
|
|
EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication,
|
|
EnvoyOpts: p.EnvoyOpts,
|
|
From: p.From,
|
|
Id: p.ID,
|
|
IdleTimeout: idleTimeout,
|
|
JwtGroupsFilter: p.JWTGroupsFilter.ToSlice(),
|
|
JwtIssuerFormat: p.JWTIssuerFormat.ToPB(),
|
|
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
|
|
KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile,
|
|
LogoUrl: p.LogoURL,
|
|
Mcp: MCPToPB(p.MCP),
|
|
Name: p.Name,
|
|
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 pb.Name == "" {
|
|
pb.Name = fmt.Sprint(p.RouteID())
|
|
}
|
|
if p.HostPathRegexRewritePattern != "" {
|
|
pb.HostPathRegexRewritePattern = proto.String(p.HostPathRegexRewritePattern)
|
|
}
|
|
if p.HostPathRegexRewriteSubstitution != "" {
|
|
pb.HostPathRegexRewriteSubstitution = proto.String(p.HostPathRegexRewriteSubstitution)
|
|
}
|
|
if p.HostRewrite != "" {
|
|
pb.HostRewrite = proto.String(p.HostRewrite)
|
|
}
|
|
if p.HostRewriteHeader != "" {
|
|
pb.HostRewriteHeader = proto.String(p.HostRewriteHeader)
|
|
}
|
|
if p.IDPClientID != "" {
|
|
pb.IdpClientId = proto.String(p.IDPClientID)
|
|
}
|
|
if p.IDPClientSecret != "" {
|
|
pb.IdpClientSecret = proto.String(p.IDPClientSecret)
|
|
}
|
|
if p.IDPAccessTokenAllowedAudiences != nil {
|
|
pb.IdpAccessTokenAllowedAudiences = &configpb.Route_StringList{
|
|
Values: slices.Clone(*p.IDPAccessTokenAllowedAudiences),
|
|
}
|
|
} else {
|
|
pb.IdpAccessTokenAllowedAudiences = nil
|
|
}
|
|
if p.Redirect != nil {
|
|
pb.Redirect = &configpb.RouteRedirect{
|
|
HttpsRedirect: p.Redirect.HTTPSRedirect,
|
|
SchemeRedirect: p.Redirect.SchemeRedirect,
|
|
HostRedirect: p.Redirect.HostRedirect,
|
|
PortRedirect: p.Redirect.PortRedirect,
|
|
PathRedirect: p.Redirect.PathRedirect,
|
|
PrefixRewrite: p.Redirect.PrefixRewrite,
|
|
ResponseCode: p.Redirect.ResponseCode,
|
|
StripQuery: p.Redirect.StripQuery,
|
|
}
|
|
} else if p.Response != nil {
|
|
pb.Response = &configpb.RouteDirectResponse{
|
|
Status: uint32(p.Response.Status),
|
|
Body: p.Response.Body,
|
|
}
|
|
} else {
|
|
to, weights, err := p.To.Flatten()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pb.To = to
|
|
pb.LoadBalancingWeights = weights
|
|
}
|
|
|
|
pb.BearerTokenFormat = p.BearerTokenFormat.ToPB()
|
|
|
|
for _, rwh := range p.RewriteResponseHeaders {
|
|
pb.RewriteResponseHeaders = append(pb.RewriteResponseHeaders, &configpb.RouteRewriteHeader{
|
|
Header: rwh.Header,
|
|
Matcher: &configpb.RouteRewriteHeader_Prefix{
|
|
Prefix: rwh.Prefix,
|
|
},
|
|
Value: rwh.Value,
|
|
})
|
|
}
|
|
|
|
return pb, nil
|
|
}
|
|
|
|
// Validate checks the validity of a policy.
|
|
func (p *Policy) Validate() error {
|
|
var err error
|
|
source, err := urlutil.ParseAndValidateURL(p.From)
|
|
if err != nil {
|
|
return fmt.Errorf("config: policy bad source url %w", err)
|
|
}
|
|
|
|
// Make sure there's no path set on the from url
|
|
if (source.Scheme == "http" || source.Scheme == "https") && !(source.Path == "" || source.Path == "/") {
|
|
return fmt.Errorf("config: policy source url (%s) contains a path, but it should be set using the path field instead",
|
|
source.String())
|
|
}
|
|
if source.Scheme == "http" {
|
|
log.Info().Msgf("config: policy source url (%s) uses HTTP but only HTTPS is supported",
|
|
source.String())
|
|
}
|
|
|
|
if len(p.To) == 0 && p.Redirect == nil && p.Response == nil {
|
|
return errEitherToOrRedirectOrResponseRequired
|
|
}
|
|
|
|
toSchemes := make(map[string]struct{})
|
|
for _, u := range p.To {
|
|
if err = u.Validate(); err != nil {
|
|
return fmt.Errorf("config: %s: %w", u.URL.String(), err)
|
|
}
|
|
toSchemes[u.URL.Scheme] = struct{}{}
|
|
}
|
|
|
|
// It is an error to mix TCP and non-TCP To URLs.
|
|
if _, hasTCP := toSchemes["tcp"]; hasTCP && len(toSchemes) > 1 {
|
|
return fmt.Errorf("config: cannot mix tcp and non-tcp To URLs")
|
|
}
|
|
if _, hasUDP := toSchemes["udp"]; hasUDP && len(toSchemes) > 1 {
|
|
return fmt.Errorf("config: cannot mix udp and non-udp To URLs")
|
|
}
|
|
|
|
if err := p.Redirect.validate(); err != nil {
|
|
return fmt.Errorf("config: %w", err)
|
|
}
|
|
|
|
// Only allow public access if no other whitelists are in place
|
|
if p.AllowPublicUnauthenticatedAccess && (p.AllowAnyAuthenticatedUser || p.AllowedDomains != nil || p.AllowedUsers != nil) {
|
|
return fmt.Errorf("config: policy route marked as public but contains whitelists")
|
|
}
|
|
|
|
// Only allow any authenticated user if no other whitelists are in place
|
|
if p.AllowAnyAuthenticatedUser && (p.AllowedDomains != nil || p.AllowedUsers != nil) {
|
|
return fmt.Errorf("config: policy route marked accessible for any authenticated user but contains whitelists")
|
|
}
|
|
|
|
if (p.TLSClientCert == "" && p.TLSClientKey != "") || (p.TLSClientCert != "" && p.TLSClientKey == "") ||
|
|
(p.TLSClientCertFile == "" && p.TLSClientKeyFile != "") || (p.TLSClientCertFile != "" && p.TLSClientKeyFile == "") {
|
|
return fmt.Errorf("config: client certificate key and cert both must be non-empty")
|
|
}
|
|
|
|
if p.TLSClientCert != "" && p.TLSClientKey != "" {
|
|
p.ClientCertificate, err = cryptutil.CertificateFromBase64(p.TLSClientCert, p.TLSClientKey)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't decode client cert %w", err)
|
|
}
|
|
} else if p.TLSClientCertFile != "" && p.TLSClientKeyFile != "" {
|
|
p.ClientCertificate, err = cryptutil.CertificateFromFile(p.TLSClientCertFile, p.TLSClientKeyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't load client cert file %w", err)
|
|
}
|
|
}
|
|
|
|
if p.TLSCustomCA != "" {
|
|
_, err := base64.StdEncoding.DecodeString(p.TLSCustomCA)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't decode custom ca: %w", err)
|
|
}
|
|
} else if p.TLSCustomCAFile != "" {
|
|
ca, err := os.ReadFile(p.TLSCustomCAFile)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't load client ca file: %w", err)
|
|
}
|
|
p.TLSCustomCA = base64.StdEncoding.EncodeToString(ca)
|
|
}
|
|
|
|
const clientCADeprecationMsg = "config: %s is deprecated, see https://www.pomerium.com/docs/" +
|
|
"reference/routes/tls#tls-downstream-client-certificate-authority for more information"
|
|
|
|
if p.TLSDownstreamClientCA != "" {
|
|
log.Info().Msgf(clientCADeprecationMsg, "tls_downstream_client_ca")
|
|
_, err := base64.StdEncoding.DecodeString(p.TLSDownstreamClientCA)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't decode downstream client ca: %w", err)
|
|
}
|
|
}
|
|
|
|
if p.TLSDownstreamClientCAFile != "" {
|
|
log.Info().Msgf(clientCADeprecationMsg, "tls_downstream_client_ca_file")
|
|
bs, err := os.ReadFile(p.TLSDownstreamClientCAFile)
|
|
if err != nil {
|
|
return fmt.Errorf("config: couldn't load downstream client ca: %w", err)
|
|
}
|
|
p.TLSDownstreamClientCA = base64.StdEncoding.EncodeToString(bs)
|
|
}
|
|
|
|
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 != "" {
|
|
return fmt.Errorf("config: only prefix_rewrite or regex_rewrite_pattern can be specified, but not both")
|
|
}
|
|
|
|
if p.Regex != "" {
|
|
rawRE := p.Regex
|
|
if !strings.HasPrefix(rawRE, "^") {
|
|
rawRE = "^" + rawRE
|
|
}
|
|
if !strings.HasSuffix(rawRE, "$") {
|
|
rawRE += "$"
|
|
}
|
|
p.compiledRegex, _ = regexp.Compile(rawRE)
|
|
}
|
|
|
|
if !p.JWTIssuerFormat.Valid() {
|
|
return fmt.Errorf("config: unsupported jwt_issuer_format value %q", p.JWTIssuerFormat)
|
|
}
|
|
|
|
if len(p.DependsOn) > 5 {
|
|
return fmt.Errorf("config: depends_on is limited to 5 additional redirect hosts, got %v", p.DependsOn)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Checksum returns the xxh3 hash for the policy.
|
|
func (p *Policy) Checksum() uint64 {
|
|
return hashutil.MustHash(p)
|
|
}
|
|
|
|
// RouteID returns a unique identifier for a route.
|
|
//
|
|
// The following fields are used to compute the ID:
|
|
// - from
|
|
// - prefix
|
|
// - path
|
|
// - regex
|
|
// - to/redirect/response (whichever is set)
|
|
func (p *Policy) RouteID() (uint64, error) {
|
|
// this function is in the hot path, try not to allocate too much memory here
|
|
hash := hashutil.NewDigest()
|
|
hash.WriteStringWithLen(p.From)
|
|
hash.WriteStringWithLen(p.Prefix)
|
|
hash.WriteStringWithLen(p.Path)
|
|
hash.WriteStringWithLen(p.Regex)
|
|
switch {
|
|
case len(p.To) > 0:
|
|
_, _ = hash.Write([]byte{1}) // case 1
|
|
hash.WriteInt32(int32(len(p.To)))
|
|
for _, to := range p.To {
|
|
hash.WriteStringWithLen(to.URL.Scheme)
|
|
hash.WriteStringWithLen(to.URL.Opaque)
|
|
if to.URL.User == nil {
|
|
_, _ = hash.Write([]byte{0})
|
|
} else {
|
|
_, _ = hash.Write([]byte{1})
|
|
hash.WriteStringWithLen(to.URL.User.Username())
|
|
p, _ := to.URL.User.Password()
|
|
hash.WriteStringWithLen(p)
|
|
}
|
|
hash.WriteStringWithLen(to.URL.Host)
|
|
hash.WriteStringWithLen(to.URL.Path)
|
|
hash.WriteStringWithLen(to.URL.RawPath)
|
|
hash.WriteBool(to.URL.OmitHost)
|
|
hash.WriteBool(to.URL.ForceQuery)
|
|
hash.WriteStringWithLen(to.URL.Fragment)
|
|
hash.WriteStringWithLen(to.URL.RawFragment)
|
|
hash.WriteUint32(to.LbWeight)
|
|
}
|
|
case p.Redirect != nil:
|
|
_, _ = hash.Write([]byte{2}) // case 2
|
|
hash.WriteBoolPtr(p.Redirect.HTTPSRedirect)
|
|
hash.WriteStringPtrWithLen(p.Redirect.SchemeRedirect)
|
|
hash.WriteStringPtrWithLen(p.Redirect.HostRedirect)
|
|
hash.WriteUint32Ptr(p.Redirect.PortRedirect)
|
|
hash.WriteStringPtrWithLen(p.Redirect.PathRedirect)
|
|
hash.WriteStringPtrWithLen(p.Redirect.PrefixRewrite)
|
|
hash.WriteInt32Ptr(p.Redirect.ResponseCode)
|
|
hash.WriteBoolPtr(p.Redirect.StripQuery)
|
|
case p.Response != nil:
|
|
_, _ = hash.Write([]byte{3}) // case 3
|
|
hash.WriteInt32(int32(p.Response.Status))
|
|
hash.WriteStringWithLen(p.Response.Body)
|
|
default:
|
|
return 0, errEitherToOrRedirectOrResponseRequired
|
|
}
|
|
return hash.Sum64(), nil
|
|
}
|
|
|
|
func (p *Policy) MustRouteID() uint64 {
|
|
id, err := p.RouteID()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func (p *Policy) String() string {
|
|
to := "?"
|
|
if len(p.To) > 0 {
|
|
var dsts []string
|
|
for _, dst := range p.To {
|
|
dsts = append(dsts, dst.URL.String())
|
|
}
|
|
to = strings.Join(dsts, ",")
|
|
}
|
|
|
|
return fmt.Sprintf("%s → %s", p.From, to)
|
|
}
|
|
|
|
// Matches returns true if the policy would match the given URL.
|
|
func (p *Policy) Matches(requestURL *url.URL, stripPort bool) bool {
|
|
// an invalid from URL should not match anything
|
|
fromURL, err := urlutil.ParseAndValidateURL(p.From)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if !FromURLMatchesRequestURL(fromURL, requestURL, stripPort) {
|
|
return false
|
|
}
|
|
|
|
if p.Prefix != "" {
|
|
if !strings.HasPrefix(requestURL.Path, p.Prefix) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if p.Path != "" {
|
|
if requestURL.Path != p.Path {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if p.compiledRegex != nil {
|
|
if !p.compiledRegex.MatchString(requestURL.Path) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsForKubernetes returns true if the policy is for kubernetes.
|
|
func (p *Policy) IsForKubernetes() bool {
|
|
return p.KubernetesServiceAccountTokenFile != "" || p.KubernetesServiceAccountToken != ""
|
|
}
|
|
|
|
// IsMCP returns true if the route is for the Model Context Protocol upstream server.
|
|
func (p *Policy) IsMCP() bool {
|
|
return p != nil && p.MCP != nil
|
|
}
|
|
|
|
// IsTCP returns true if the route is for TCP.
|
|
func (p *Policy) IsTCP() bool {
|
|
return strings.HasPrefix(p.From, "tcp")
|
|
}
|
|
|
|
// IsTCPUpstream returns true if the route has a TCP upstream (To) URL
|
|
func (p *Policy) IsTCPUpstream() bool {
|
|
return len(p.To) > 0 && p.To[0].URL.Scheme == "tcp"
|
|
}
|
|
|
|
// IsUDP returns true if the route is for UDP.
|
|
func (p *Policy) IsUDP() bool {
|
|
return strings.HasPrefix(p.From, "udp")
|
|
}
|
|
|
|
// IsUDPUpstream returns true if the route has a UDP upstream (To) URL
|
|
func (p *Policy) IsUDPUpstream() bool {
|
|
return len(p.To) > 0 && p.To[0].URL.Scheme == "udp"
|
|
}
|
|
|
|
// AllAllowedDomains returns all the allowed domains.
|
|
func (p *Policy) AllAllowedDomains() []string {
|
|
var ads []string
|
|
ads = append(ads, p.AllowedDomains...)
|
|
for _, sp := range p.SubPolicies {
|
|
ads = append(ads, sp.AllowedDomains...)
|
|
}
|
|
return ads
|
|
}
|
|
|
|
// AllAllowedIDPClaims returns all the allowed IDP claims.
|
|
func (p *Policy) AllAllowedIDPClaims() []identity.FlattenedClaims {
|
|
var aics []identity.FlattenedClaims
|
|
if len(p.AllowedIDPClaims) > 0 {
|
|
aics = append(aics, p.AllowedIDPClaims)
|
|
}
|
|
for _, sp := range p.SubPolicies {
|
|
if len(sp.AllowedIDPClaims) > 0 {
|
|
aics = append(aics, sp.AllowedIDPClaims)
|
|
}
|
|
}
|
|
return aics
|
|
}
|
|
|
|
// AllAllowedUsers returns all the allowed users.
|
|
func (p *Policy) AllAllowedUsers() []string {
|
|
var aus []string
|
|
aus = append(aus, p.AllowedUsers...)
|
|
for _, sp := range p.SubPolicies {
|
|
aus = append(aus, sp.AllowedUsers...)
|
|
}
|
|
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 {
|
|
if p != nil && p.PassIdentityHeaders != nil {
|
|
return *p.PassIdentityHeaders
|
|
}
|
|
|
|
if options != nil && options.PassIdentityHeaders != nil {
|
|
return *options.PassIdentityHeaders
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetFrom gets the from URL.
|
|
func (p *Policy) GetFrom() string {
|
|
return p.From
|
|
}
|
|
|
|
// GetPath gets the path.
|
|
func (p *Policy) GetPath() string {
|
|
return p.Path
|
|
}
|
|
|
|
// GetPrefix gets the prefix.
|
|
func (p *Policy) GetPrefix() string {
|
|
return p.Prefix
|
|
}
|
|
|
|
// GetRegex gets the regex.
|
|
func (p *Policy) GetRegex() string {
|
|
return p.Regex
|
|
}
|
|
|
|
/*
|
|
SortPolicies sorts policies to match the following SQL order:
|
|
|
|
ORDER BY from ASC,
|
|
path DESC NULLS LAST,
|
|
regex_priority_order DESC NULLS LAST,
|
|
regex DESC NULLS LAST
|
|
prefix DESC NULLS LAST,
|
|
id ASC
|
|
*/
|
|
func SortPolicies(pp []Policy) {
|
|
sort.SliceStable(pp, func(i, j int) bool {
|
|
strDesc := func(a, b string) (val bool, equal bool) {
|
|
return a > b, a == b
|
|
}
|
|
|
|
strAsc := func(a, b string) (val bool, equal bool) {
|
|
return a < b, a == b
|
|
}
|
|
|
|
intPDesc := func(a, b *int64) (val bool, equal bool) {
|
|
if a == nil && b == nil {
|
|
return false, true
|
|
}
|
|
if a == nil && b != nil {
|
|
return false, false
|
|
}
|
|
if a != nil && b == nil {
|
|
return true, false
|
|
}
|
|
return *a > *b, *a == *b
|
|
}
|
|
|
|
if val, equal := strAsc(pp[i].From, pp[j].From); !equal {
|
|
return val
|
|
}
|
|
|
|
if val, equal := strDesc(pp[i].Path, pp[j].Path); !equal {
|
|
return val
|
|
}
|
|
|
|
if val, equal := intPDesc(pp[i].RegexPriorityOrder, pp[j].RegexPriorityOrder); !equal {
|
|
return val
|
|
}
|
|
|
|
if val, equal := strDesc(pp[i].Regex, pp[j].Regex); !equal {
|
|
return val
|
|
}
|
|
|
|
if val, equal := strDesc(pp[i].Prefix, pp[j].Prefix); !equal {
|
|
return val
|
|
}
|
|
|
|
return pp[i].ID < pp[j].ID // Ascending order for ID
|
|
})
|
|
}
|