Skip to content

Commit

Permalink
feat: basic auth impl (envoyproxy#2224)
Browse files Browse the repository at this point in the history
* basic auth impl

Signed-off-by: huabing zhao <zhaohuabing@gmail.com>

add referenced secrets to resource

Signed-off-by: huabing zhao <zhaohuabing@gmail.com>

change string to []byte

Signed-off-by: huabing zhao <zhaohuabing@gmail.com>

* e2e test

Signed-off-by: huabing zhao <zhaohuabing@gmail.com>

---------

Signed-off-by: huabing zhao <zhaohuabing@gmail.com>
  • Loading branch information
zhaohuabing authored Nov 28, 2023
1 parent 77656f8 commit f4f45be
Show file tree
Hide file tree
Showing 24 changed files with 1,023 additions and 119 deletions.
2 changes: 2 additions & 0 deletions api/v1alpha1/basic_auth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package v1alpha1

import gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"

const BasicAuthUsersSecretKey = ".htpasswd"

// BasicAuth defines the configuration for the HTTP Basic Authentication.
type BasicAuth struct {
// The Kubernetes secret which contains the username-password pairs in
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
require (
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4
github.com/davecgh/go-spew v1.1.1
github.com/envoyproxy/go-control-plane v0.11.2-0.20231020171731-dd48dc81e5ce
github.com/envoyproxy/go-control-plane v0.11.2-0.20231116045842-b54b6db2c2a8
github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7
github.com/evanphx/json-patch/v5 v5.7.0
github.com/go-logfmt/logfmt v0.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.11.2-0.20231020171731-dd48dc81e5ce h1:Nk6X9GLZoScphDZOVWJ8zodLPrv0aXmscdIMGZQDR3c=
github.com/envoyproxy/go-control-plane v0.11.2-0.20231020171731-dd48dc81e5ce/go.mod h1:3X10o7QcAVxP4y/hnTLgkXLwuZV2DxAEh6uaYD5PoxI=
github.com/envoyproxy/go-control-plane v0.11.2-0.20231116045842-b54b6db2c2a8 h1:HvQxuGnVQ7zCIz2B90WuTfOcJhoLW1d3u1NQ8j0T9BU=
github.com/envoyproxy/go-control-plane v0.11.2-0.20231116045842-b54b6db2c2a8/go.mod h1:3X10o7QcAVxP4y/hnTLgkXLwuZV2DxAEh6uaYD5PoxI=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
Expand Down
57 changes: 52 additions & 5 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ func (t *Translator) translateSecurityPolicyForRoute(
cors *ir.CORS
jwt *ir.JWT
oidc *ir.OIDC
basicAuth *ir.BasicAuth
err, errs error
)

Expand All @@ -282,6 +283,12 @@ func (t *Translator) translateSecurityPolicyForRoute(
}
}

if policy.Spec.BasicAuth != nil {
if basicAuth, err = t.buildBasicAuth(policy, resources); err != nil {
errs = multierror.Append(errs, err)
}
}

// Apply IR to all relevant routes
// Note: there are multiple features in a security policy, even if some of them
// are invalid, we still want to apply the valid ones.
Expand All @@ -296,6 +303,7 @@ func (t *Translator) translateSecurityPolicyForRoute(
r.CORS = cors
r.JWT = jwt
r.OIDC = oidc
r.BasicAuth = basicAuth
}
}
}
Expand All @@ -311,6 +319,7 @@ func (t *Translator) translateSecurityPolicyForGateway(
cors *ir.CORS
jwt *ir.JWT
oidc *ir.OIDC
basicAuth *ir.BasicAuth
err, errs error
)

Expand All @@ -326,8 +335,13 @@ func (t *Translator) translateSecurityPolicyForGateway(
}

if policy.Spec.OIDC != nil {
oidc, err = t.buildOIDC(policy, resources)
if err != nil {
if oidc, err = t.buildOIDC(policy, resources); err != nil {
errs = multierror.Append(errs, err)
}
}

if policy.Spec.BasicAuth != nil {
if basicAuth, err = t.buildBasicAuth(policy, resources); err != nil {
errs = multierror.Append(errs, err)
}
}
Expand All @@ -336,9 +350,6 @@ func (t *Translator) translateSecurityPolicyForGateway(
// If the feature is already set, then skip it, since it must have be
// set by a policy attaching to the route
//
// It can be difficult to reason about the state of the system if we apply
// part of the policy and not the rest. Therefore, we either apply all of it
// or none of it (when get errors when translating the policy)
// Note: there are multiple features in a security policy, even if some of them
// are invalid, we still want to apply the valid ones.
irKey := t.getIRKey(gateway.Gateway)
Expand All @@ -357,6 +368,9 @@ func (t *Translator) translateSecurityPolicyForGateway(
if r.OIDC == nil {
r.OIDC = oidc
}
if r.BasicAuth == nil {
r.BasicAuth = basicAuth
}
}
}
return errs
Expand Down Expand Up @@ -546,3 +560,36 @@ func validateTokenEndpoint(tokenEndpoint string) error {
}
return nil
}

func (t *Translator) buildBasicAuth(
policy *egv1a1.SecurityPolicy,
resources *Resources) (*ir.BasicAuth, error) {
var (
basicAuth = policy.Spec.BasicAuth
usersSecret *v1.Secret
err error
)

from := crossNamespaceFrom{
group: egv1a1.GroupName,
kind: KindSecurityPolicy,
namespace: policy.Namespace,
}
if usersSecret, err = t.validateSecretRef(
false, from, basicAuth.Users, resources); err != nil {
return nil, err
}

usersSecretBytes, ok := usersSecret.Data[egv1a1.BasicAuthUsersSecretKey]
if !ok || len(usersSecretBytes) == 0 {
return nil, fmt.Errorf(
"users secret not found in secret %s/%s",
usersSecret.Namespace, usersSecret.Name)
}

if err != nil {
return nil, err
}

return &ir.BasicAuth{Users: usersSecretBytes}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
secrets:
- apiVersion: v1
kind: Secret
metadata:
namespace: default
name: users-secret
data:
.htpasswd: "dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo="
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/foo"
backendRefs:
- name: service-1
port: 8080
securityPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
namespace: default
name: policy-for-http-route
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: httproute-1
namespace: default
basicAuth:
users:
name: "users-secret"
152 changes: 152 additions & 0 deletions internal/gatewayapi/testdata/securitypolicy-with-basic-auth.out.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
creationTimestamp: null
name: gateway-1
namespace: envoy-gateway
spec:
gatewayClassName: envoy-gateway-class
listeners:
- allowedRoutes:
namespaces:
from: All
name: http
port: 80
protocol: HTTP
status:
listeners:
- attachedRoutes: 1
conditions:
- lastTransitionTime: null
message: Sending translated listener configuration to the data plane
reason: Programmed
status: "True"
type: Programmed
- lastTransitionTime: null
message: Listener has been successfully translated
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Listener references have been resolved
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
name: http
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
- group: gateway.networking.k8s.io
kind: GRPCRoute
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
creationTimestamp: null
name: httproute-1
namespace: default
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- name: gateway-1
namespace: envoy-gateway
sectionName: http
rules:
- backendRefs:
- name: service-1
port: 8080
matches:
- path:
value: /foo
status:
parents:
- conditions:
- lastTransitionTime: null
message: Route is accepted
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: null
message: Resolved all the Object references for the Route
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
name: gateway-1
namespace: envoy-gateway
sectionName: http
infraIR:
envoy-gateway/gateway-1:
proxy:
listeners:
- address: ""
ports:
- containerPort: 10080
name: http
protocol: HTTP
servicePort: 80
metadata:
labels:
gateway.envoyproxy.io/owning-gateway-name: gateway-1
gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
name: envoy-gateway/gateway-1
securityPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
creationTimestamp: null
name: policy-for-http-route
namespace: default
spec:
basicAuth:
users:
group: null
kind: null
name: users-secret
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: httproute-1
namespace: default
status:
conditions:
- lastTransitionTime: null
message: SecurityPolicy has been accepted.
reason: Accepted
status: "True"
type: Accepted
xdsIR:
envoy-gateway/gateway-1:
accessLog:
text:
- path: /dev/stdout
http:
- address: 0.0.0.0
hostnames:
- '*'
isHTTP2: false
name: envoy-gateway/gateway-1/http
port: 10080
routes:
- backendWeights:
invalid: 0
valid: 0
basicAuth:
users: dXNlcjE6e1NIQX10RVNzQm1FL3lOWTNsYjZhMEw2dlZRRVpOcXc9CnVzZXIyOntTSEF9RUo5TFBGRFhzTjl5blNtYnh2anA3NUJtbHg4PQo=
destination:
name: httproute/default/httproute-1/rule/0
settings:
- endpoints:
- host: 7.7.7.7
port: 8080
protocol: HTTP
weight: 1
hostname: gateway.envoyproxy.io
name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
pathMatch:
distinct: false
name: ""
prefix: /foo
13 changes: 13 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ func (x Xds) Printable() *Xds {
if route.OIDC != nil {
route.OIDC.ClientSecret = []byte{}
}
if route.BasicAuth != nil {
route.BasicAuth.Users = []byte{}
}
}
}
return out
Expand Down Expand Up @@ -295,6 +298,8 @@ type HTTPRoute struct {
OIDC *OIDC `json:"oidc,omitempty" yaml:"oidc,omitempty"`
// Proxy Protocol Settings
ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty"`
// BasicAuth defines the schema for the HTTP Basic Authentication.
BasicAuth *BasicAuth `json:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
// ExtensionRefs holds unstructured resources that were introduced by an extension and used on the HTTPRoute as extensionRef filters
ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"`
}
Expand Down Expand Up @@ -357,6 +362,14 @@ type OIDC struct {
Scopes []string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
}

// BasicAuth defines the schema for the HTTP Basic Authentication.
//
// +k8s:deepcopy-gen=true
type BasicAuth struct {
// The username-password pairs in htpasswd format.
Users []byte `json:"users,omitempty" yaml:"users,omitempty"`
}

type OIDCProvider struct {
// The OIDC Provider's [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint).
AuthorizationEndpoint string `json:"authorizationEndpoint,omitempty"`
Expand Down
Loading

0 comments on commit f4f45be

Please sign in to comment.