blob: cf83ad72d47e5d320aacb40e26893f4ef5d3fef0 [file] [log] [blame] [view]
---
title: Configuring Mutual Authentication via ApisixTls
---
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->
In this practice, we will use mTLS to protect our exposed ingress APIs.
To learn more about mTLS, please refer to [Mutual authentication](https://en.wikipedia.org/wiki/Mutual_authentication)
## Prerequisites
- an available Kubernetes cluster
- an available APISIX and APISIX Ingress Controller installation
In this guide, we assume that your APISIX is installed in the `apisix` namespace and `ssl` is enabled, which is not enabled by default in the Helm Chart. To enable it, you need to set `gateway.tls.enabled=true` during installation.
Assuming the SSL port is `9443`.
## Deploy httpbin service
We use [kennethreitz/httpbin](https://hub.docker.com/r/kennethreitz/httpbin/) as the service image, See its overview page for details.
Deploy it to the default namespace:
```shell
kubectl run httpbin --image kennethreitz/httpbin --port 80
kubectl expose pod httpbin --port 80
```
## Route the traffic
Since SSL is not configured in ApisixRoute, we can use the config similar to the one in practice [Proxy the httpbin service](./proxy-the-httpbin-service.md).
```yaml
# route.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpserver-route
spec:
http:
- name: httpbin
match:
hosts:
- mtls.httpbin.local
paths:
- "/*"
backends:
- serviceName: httpbin
servicePort: 80
```
Please remember the host field is `mtls.httpbin.local`. It will be the domain we are going to use.
Test it:
```bash
kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl "http://127.0.0.1:9080/ip" -H "Host: mtls.httpbin.local"
```
It should output:
```json
{
"origin": "127.0.0.1"
}
```
## Certificates
Before configuring SSL, we must have certificates. Certificates often authorized by certificate provider, which also known as Certification Authority (CA).
You can use [OpenSSL](https://en.wikipedia.org/wiki/Openssl) to generate self-signed certificates for testing purposes. Some pre-generated certificates for this guide are [here](https://github.com/apache/apisix-ingress-controller/tree/master/docs/en/latest/tutorials/mtls).
- `ca.pem`: The root CA.
- `server.pem` and `server.key`: Server certificate used to enable SSL (https). Contains correct `subjectAltName` matches domain `mtls.httpbin.local`.
- `user.pem` and `user.key`: Client certificate.
To verify them, use commands below:
```bash
openssl verify -CAfile ./ca.pem ./server.pem
openssl verify -CAfile ./ca.pem ./user.pem
```
## Protecting the Route with SSL
In APISIX Ingress Controller, we use the `ApisixTls` resource to protect our routes with SSL. This resource requires a secret containing the certificate and private key.
**What is the Secret?**
The `ApisixTls` resource needs a Kubernetes secret that contains the SSL certificate and the private key. The `cert` field of the secret should contain the SSL certificate in PEM format, while the `key` field should contain the private key in PEM format. These values must be base64-encoded before storing them in the Kubernetes secret.
Here's how to generate the base64 value of the certificate and private key:
```bash
base64 -w0 foo.crt # for cert
base64 -w0 foo.key # for private key
```
Replace the `foo.crt` and `foo.key` with the actual names of certificate and private key files, respectively.
**Example Keys and Certificates**
The examples we use in this guide are available on [GitHub](https://github.com/apache/apisix-ingress-controller/tree/master/docs/en/latest/tutorials/mtls).
**Creating the Secret**
To create the secret, run the following command:
```bash
kubectl apply -f ./mtls/server-secret.yaml -n default
```
This creates a Kubernetes secret with the name `server-secret` in the `default` namespace. We will reference this secret in the `ApisixTls` resource.
**Creating the `ApisixTls` Resource**
Next, create the ApisixTls resource to use the certificate and private key from the secret:
```yaml
# tls.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
name: sample-tls
spec:
hosts:
- mtls.httpbin.local
secret:
name: server-secret
namespace: default
```
The `spec` field contains the details of the `ApisixTls` resource, such as the `hosts` and `secret` fields. In this example, we are specifying that the SSL certificate should be used for the domain `mtls.httpbin.local`.
Apply this YAML file to create the `ApisixTls` resource:
```bash
kubectl apply -f tls.yaml -n default
```
**Testing the SSL Configuration**
Now since we've configured SSL, we can test it out by sending a request to the protected route. To do this, we'll use the `curl` command.
```bash
kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k
```
Here are some important points to keep in mind:
- We're using the `--resolve` parameter to resolve our domain name. This tells `curl` to resolve `mtls.httpbin.local:9443 to 127.0.0.1`.
- We're using the https protocol and the `SSL` port `9443`.
- We're using the `-k` parameter to allow insecure connections when using `SSL`. Since our self-signed certificate is not trusted by default, we need to allow insecure connections.
Without the domain mtls.httpbin.local, the request won't succeed.
You can add the `-v` parameter to log the handshake process.
Now, we configured SSL successfully.
## Mutual Authentication
Like `server-secret`, we will create a `client-ca-secret` to store the CA that verify the certificate client presents.
The keys and certificates used in the examples [are here](https://github.com/apache/apisix-ingress-controller/tree/master/docs/en/latest/tutorials/mtls).
```bash
kubectl apply -f ./mtls/client-ca-secret.yaml -n default
```
Then, change our ApisixTls and apply it:
```yaml
# mtls.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
name: sample-tls
spec:
hosts:
- mtls.httpbin.local
secret:
name: server-secret
namespace: default
client:
caSecret:
name: client-ca-secret
namespace: default
depth: 10
```
The `client` field references the secret, `depth` indicates the max certificate chain length.
Let's try to connect the route without any chanegs:
```bash
kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k
```
If everything works properly, it will return a `400 Bad Request`.
From APISIX access log, we could find logs like this:
```log
2021/05/27 17:20:54 [error] 43#43: *106132 [lua] init.lua:293: http_access_phase(): client certificate was not present, client: 127.0.0.1, server: _, request: "GET /ip HTTP/2.0", host: "mtls.httpbin.local:9443"
127.0.0.1 - - [27/May/2021:17:20:54 +0000] mtls.httpbin.local:9443 "GET /ip HTTP/2.0" 400 154 0.000 "-" "curl/7.76.1" - - - "http://mtls.httpbin.local:9443"
```
That means our mutual authentication has been enabled successfully.
Now, we need to transfer our client cert to the APISIX container to verify the mTLS functionality.
The keys and certificates used in the examples [are here](https://github.com/apache/apisix-ingress-controller/tree/master/docs/en/latest/tutorials/mtls).
```bash
# Transfer client certificate
kubectl -n apisix cp ./user.key <APISIX_POD_NAME>:/tmp/user.key
kubectl -n apisix cp ./user.pem <APISIX_POD_NAME>:/tmp/user.pem
# Test
kubectl -n apisix exec -it <APISIX_POD_NAME> -- curl --resolve 'mtls.httpbin.local:9443:127.0.0.1' "https://mtls.httpbin.local:9443/ip" -k --cert /tmp/user.pem --key /tmp/user.key
```
Parameter `--cert` and `--key` indicates our certificate and key path.
It should output normally:
```json
{
"origin": "127.0.0.1"
}
```