Hi, there
I'm devoting myself to implementing the authentication capability in
APISIX Ingress Controller, and below is my proposal.
Background
Authentication and Authorization are required on the Kubernetes
Ingress layer, which is similar to API Gateway. However, due to the
lack of some APISIX Consumer alike stuff, APISIX Ingress Controller
cannot leverage any Authentication plugins provided by APISIX.
That’s why an appropriate way needs to be developed to make up for
this drawback.
Research
The standard Ingress isn’t involve anything about Authentication, thus
every Ingress Controllers implement it in different ways.
Kong
As an API Gateway solution, Kong Ingress Controller implements the
KongConsumer CRD, which maps it to the Consumer object in Kong.
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: <object name>
namespace: <object namespace>
annotations:
kubernetes.io/ingress.class: <controller ingress class, "kong" by default>
username: <user name>
custom_id: <custom ID>
All KongConsumer resources will be watched by Kong Ingress Controller
and used when authenticating.
Ingress Nginx
The Authentication is weak in Ingress Nginx,only basic-auth and digest
authentication are supported. It is implemented by specifying
annotations in Ingress.
nginx.ingress.kubernetes.io/auth-type: [basic|digest]
This annotation specifies the authentication type.
nginx.ingress.kubernetes.io/auth-secret: secretName
And this annotation specifies the Kubernetes Secret name which stores
the username and password.
apiVersion: v1
data:
auth: Zm9vOiRhcHIxJE9GRzNYeWJwJGNrTDBGSERBa29YWUlsSDkuY3lzVDAK
kind: Secret
metadata:
name: basic-auth
namespace: default
type: Opaque
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: http-svc
servicePort: 80
How Consumers Work in APISIX
Consumers are always used with Authentication, you need to configure a
consumer (with username) and enable several plugins on it, then also
enabling these (auth) plugins in the Route. APISIX searches the
consumer according to the auth-type and its key (headers or query
strings).
Frankly, it also works if we just copy this way to APISIX Ingress
Controller, but the configure is not so clean and easy. As we first
need some kinds of stuff to create the Consumer, then enable (APISIX)
plugins on it, and enabling these plugins on ApisixRoute again. We
need an easier way.
ApisixConsumer
ApisixConsumer is necessary as we have to create the consumer.
apiVersion: apisix.apache.org/v2alpha1
kind: ApisixConsumer
metadata:
name: <consumer-name>
namespace: <consumer-namespace>
labels:
group: 111
spec:
authParameter:
basicAuth:
valueRef: # exclusive with value
kind: secret
name: <secret-name>
namespace: <secret-namespace>
value: # exclusive with valueRef
username: aaa
password: xxx
keyAuth:
key: xxxx # exclusive with keyRef
keyRef: # exclusive with key
kind: secret
name: <secret-name>
namespace: <secret-namespace>
Currently, only fields under authParameter are concerned (other
features like limit-rate, can be put under rateLimiting field and so
on), which represents authentications, more auth types can be
implemented there, values are kind-dependent, for instance, basic auth
needs a username and password configuration in value or importing from
a secret (exclusively), in valueRef.
All ApisixConsumers will be watched by APISIX Ingress Controller, and
corresponding Consumer objects will be created, which name is
concatenated by the namespace and name (so it can be unique).
All authentication configurations will be translated to the
corresponding underlying plugins.
You may wonder that why not just using the native plugin way here.
This is because the fields above (e.g. password in basic auth) are
sensitive and just configuring literally is not so safe, a better way
is fetching them from Kubernetes secret, also, the native plugin
configuring is too flexible to manage, it might results in some
security issues (since validation is not so strong so far).
ApisixRoute
On the other hand, authentication should be enabled in ApisixRoute, of
course, it can be supported by configuring the plugins field:
apiVersion: apisix.apache.org/v2alpha1
kind: ApisixRoute
metadata:
name: test
spec:
http:
- name: rule1
match:
paths:
- /*
backend:
serviceName: httpbin
servicePort: 8080
plugins:
- name: basic-auth
enable: true
Considering some capabilities like consumer restriction (ACL) and a
unifying way for management, native fields will be better here.
apiVersion: apisix.apache.org/v2alpha1
kind: ApisixRoute
metadata:
name: test
spec:
http:
- name: rule1
match:
paths:
- /*
backend:
serviceName: httpbin
servicePort: 8080
authentication:
enable: true
type: keyAuth
keyAuth:
header: Authorization
acl:
allowedConsumers:
selector:
group: 111
deniedConsumers:
selector:
group: 222
rejectedCode: 503
Just enabling the required authentication way in ApisixRoute and
configuring the ACL (consumer restriction) on demand.
In practice, people won’t configure more than one authentication
method in a Route, so here the authentication type must be configured,
any extra configurations can be put under another field which name is
the same as the authentication type (e.g. configurations for keyAuth
is under keyAuth field).
As for ACL, Kuberentes typical label-selector is applied as the
consumer list might be huge. In this case, it’s not easy to maintain
such an ApisixRoute object.
All the authentications and ACL will be translated to the underlying
APISIX plugins.
Thanks!
Chao Zhang
GitHub: https://github.com/tokers