Skip to content

Commit

Permalink
Merge pull request #1672 from NCEAS/feature-1665-x509-certs
Browse files Browse the repository at this point in the history
Feature 1665 x509 certs
  • Loading branch information
artntek authored Aug 7, 2023
2 parents 0ddbd3e + 322cc43 commit 6568b06
Show file tree
Hide file tree
Showing 15 changed files with 555 additions and 82 deletions.
3 changes: 3 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ USER metacat
WORKDIR "$TC_HOME"/bin

EXPOSE 8080
EXPOSE 5005
EXPOSE 5701
EXPOSE 5702
EXPOSE 5703

LABEL org.opencontainers.image.source="https://github.com/NCEAS/metacat"

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

CMD ["catalina.sh","start"]
55 changes: 40 additions & 15 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,47 @@

set -e

enableRemoteDebugging() {
# Allow remote debugging via port 5005
# TODO: for JDK > 8, may need to change [...]address=5005 to [...]address=*:5005 --
# see https://bugs.openjdk.org/browse/JDK-8175050
{
echo "# Allow remote debugging connections to the port listed as \"address=\" below:"
echo "export CATALINA_OPTS=\"${CATALINA_OPTS} \
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005\""
} > "${TC_HOME}"/bin/setenv.sh
echo
echo "* * * * * * Remote debugging connections enabled on port 5005 * * * * * *"
echo
}

configMetacatUi() {
# show default skin if nothing else configured.
# 1. Overwrite config.js
{
echo "MetacatUI.AppConfig = {"
echo " theme: \"default\","
echo " root: \"/metacatui\","
echo " metacatContext: \"/${METACAT_APP_CONTEXT}\","
S=""
if [ "$METACAT_EXTERNAL_PORT" == "443" ] || [ "$METACAT_EXTERNAL_PORT" == "8443" ]; then
S="s"
fi
echo " baseUrl: \"http${S}://$METACAT_EXTERNAL_HOSTNAME:$METACAT_EXTERNAL_PORT\""
echo "}"
} > "${TC_HOME}"/webapps/metacatui/config/config.js

# 2. edit index.html to point to it
sed -i 's|"/config/config.js"|"./config/config.js"|g' "${TC_HOME}"/webapps/metacatui/index.html
}

if [[ $DEVTOOLS == "true" ]]; then
echo '* * * Container "-devtools" mode'
echo '* * * NOTE Tomcat does NOT get started in devtools mode!'
echo '* * * See commands in /usr/local/bin/docker-entrypoint.sh to start manually'
echo '* * *'
echo '* * * starting infinite loop -- ctrl-c to interrupt...'
enableRemoteDebugging
sh -c 'trap "exit" TERM; while true; do sleep 1; done'

elif [[ $1 = "catalina.sh" ]]; then
Expand All @@ -35,19 +70,7 @@ elif [[ $1 = "catalina.sh" ]]; then
unzip "${TC_HOME}"/webapps/metacatui.war -d "${TC_HOME}"/webapps/metacatui
fi

# show KNB skin if nothing else configured.
# 1. Overwrite config.js
{
echo "MetacatUI.AppConfig = {"
echo " theme: \"knb\","
echo " root: \"/metacatui\","
echo " metacatContext: \"/${METACAT_APP_CONTEXT}\","
echo " baseUrl: \"http://$METACAT_EXTERNAL_HOSTNAME:$METACAT_EXTERNAL_PORT\""
echo "}"
} > "${TC_HOME}"/webapps/metacatui/config/config.js

# 2. edit index.html to point to it
sed -i 's|"/config/config.js"|"./config/config.js"|g' "${TC_HOME}"/webapps/metacatui/index.html
configMetacatUi

# set the env vars for metacat location. Note that TC_HOME is set in the Dockerfile
METACAT_DEFAULT_WAR=${TC_HOME}/webapps/metacat.war
Expand Down Expand Up @@ -80,13 +103,15 @@ elif [[ $1 = "catalina.sh" ]]; then
/var/metacat/config \
/var/metacat/.metacat

# if METACAT_DEBUG, set the root log level to "DEBUG"
# if METACAT_DEBUG, set the root log level to "DEBUG" and enable
# remote debugging connections to tomcat
if [[ $METACAT_DEBUG == "true" ]]; then
sed -i 's/rootLogger\.level[^\n]*/rootLogger\.level=DEBUG/g' \
"${TC_HOME}"/webapps/metacat/WEB-INF/classes/log4j2.properties;
echo
echo "* * * * * * set Log4J rootLogger level to DEBUG * * * * * *"
echo
enableRemoteDebugging
fi

# TODO: need a more-elegant way to handle this, without manipulating files
Expand Down Expand Up @@ -160,7 +185,7 @@ ${METACAT_ADMINISTRATOR_PASSWORD}&username=${METACAT_ADMINISTRATOR_USERNAME}" \
echo 'FAILED - unable to log in as admin'
echo '**************************************'
echo "grepping log $TC_HOME/logs/localhost*.$(date +%Y-%m-%d).log for startup conditions..."
exec grep -B7 -A20 "FATAL" "$TC_HOME"/logs/*
grep -B7 -A20 "FATAL" "$TC_HOME"/logs/*
echo
echo '**************************************'
echo
Expand Down
202 changes: 183 additions & 19 deletions helm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ For now, you need to have an existing instance of
[configured for metacat](https://knb.ecoinformatics.org/knb/docs/install.html#solr-server).
Starting in the root directory of the `metacat` repo:

```console
```shell
# 1. build metacat's binary distribution
$ ant distbin

Expand Down Expand Up @@ -50,7 +50,7 @@ metacat, and with its index

To install the chart with the release name `my-release`:

```console
```shell
helm install my-release ./helm
```

Expand All @@ -63,15 +63,15 @@ installation.
Parameters may be provided on the command line to override those in values.yaml; e.g.

```console
```shell
helm install my-release ./helm --set postgres.auth.existingSecret=my-release-secrets
```

## Uninstalling the Chart

To uninstall/delete the `my-release` deployment:

```console
```shell
helm delete my-release
```

Expand All @@ -81,7 +81,7 @@ exception of Secrets, PVCs and PVs) and deletes the release.
There are two PVCs associated with `my-release`; one for Metacat data files, and the other for
the PostgreSQL database (if the postgres sub-chart is enabled). To delete:

```console
```shell
kubectl delete pvc <myMetacatPVCName> (or <myPostgresPVCName>) ## deletes named PVC
or:
kubectl delete pvc -l release=my-release ## deletes both
Expand All @@ -94,17 +94,18 @@ kubectl delete pvc -l release=my-release ## deletes both

### Metacat Application-Specific Properties

| Name | Description | Value |
|----------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
| `metacat.application.context` | The application context to use | `metacat` |
| `metacat.administrator.username` | The admin username that will be used to authenticate | `admin@localhost` |
| `metacat.auth.administrators` | A colon-separated list of admin usernames or LDAP-style DN | `admin@localhost:uid=jones,ou=Account,dc=ecoinformatics,dc=org` |
| `metacat.database.connectionURI` | postgres DB URI (RELEASE PREFIX, or blank for sub-chart) | `jdbc:postgresql://mc-postgresql/metacat` |
| `metacat.guid.doi.enabled` | Allow users to publish Digital Object Identifiers at doi.org? | `true` |
| `metacat.server.httpPort` | The http port exposed externally by the Ingress or Service | `80` |
| `metacat.server.name` | The hostname for the server, as exposed by the ingress | `localhost` |
| `metacat.solr.baseURL` | The url to access solr | `http://host.docker.internal:8983/solr` |
| `metacat.replication.logdir` | Location for the replication logs | `/var/metacat/logs` |
| Name | Description | Value |
|------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------|
| `metacat.application.context` | The application context to use | `metacat` |
| `metacat.administrator.username` | The admin username that will be used to authenticate | `admin@localhost` |
| `metacat.auth.administrators` | A colon-separated list of admin usernames or LDAP-style DN | `admin@localhost:uid=jones,ou=Account,dc=ecoinformatics,dc=org` |
| `metacat.database.connectionURI` | postgres DB URI (RELEASE PREFIX, or blank for sub-chart) | `jdbc:postgresql://mc-postgresql/metacat` |
| `metacat.guid.doi.enabled` | Allow users to publish Digital Object Identifiers at doi.org? | `true` |
| `metacat.server.httpPort` | The http port exposed externally, if NOT using the ingress | `""` |
| `metacat.server.name` | The hostname for the server, as exposed by the ingress | `localhost` |
| `metacat.solr.baseURL` | The url to access solr | `http://host.docker.internal:8983/solr` |
| `metacat.replication.logdir` | Location for the replication logs | `/var/metacat/logs` |
| `metacat.dataone.certificate.fromHttpHeader.enabled` | Enable mutual auth with client certs | `false` |

### Metacat Image, Container & Pod Parameters

Expand All @@ -116,6 +117,7 @@ kubectl delete pvc -l release=my-release ## deletes both
| `image.tag` | Overrides the image tag. Will default to the chart appVersion if set to "" | `DEVELOP` |
| `image.debug` | Specify if container debugging should be enabled (sets log level to "DEBUG") | `false` |
| `imagePullSecrets` | Optional list of references to secrets in the same namespace | `[]` |
| `container.ports` | Optional list of additional container ports to expose within the cluster | `[]` |
| `serviceAccount.create` | Should a service account be created to run Metacat? | `false` |
| `serviceAccount.annotations` | Annotations to add to the service account | `{}` |
| `serviceAccount.name` | The name to use for the service account. | `""` |
Expand Down Expand Up @@ -145,6 +147,7 @@ kubectl delete pvc -l release=my-release ## deletes both
| `ingress.hosts` | A collection of rules mapping different hosts to the backend. | `[]` |
| `ingress.annotations` | Annotations for the ingress | `{}` |
| `ingress.tls` | The TLS configuration | `[]` |
| `ingress.d1CaCertSecretName` | Name of Secret containing DataONE CA certificate | `ca-secret` |
| `service.enabled` | Enable another optional service in addition to headless svc | `false` |
| `service.type` | Kubernetes Service type. Defaults to ClusterIP if not set | `LoadBalancer` |
| `service.clusterIP` | IP address of the service. Auto-generated if not set | `""` |
Expand Down Expand Up @@ -191,14 +194,14 @@ automatically each time you deploy.
Parameters may be provided on the command line to override those in [values.yaml](./values.yaml);
for example:

```console
```shell
helm install my-release ./helm --set metacat.solr.baseURL=http://mysolrhost:8983/solr
```

Alternatively, a YAML file that specifies the values for the parameters can be provided
while installing the chart. For example:

```console
```shell
helm install my-release -f myValues.yaml ./helm
```

Expand Down Expand Up @@ -244,10 +247,171 @@ path in the metacat container.

The PostgreSQL image stores the database data at the `/bitbami/pgdata` path in its own container.

## Networking
## Networking and x.509 Certificates

By default, the chart will install an
[Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) (see the `ingress.*`
parameters under [Networking & Monitoring](#networking--monitoring)), which will expose HTTP
and HTTPS routes from outside the cluster to the Metacat application within the cluster. Note that
your cluster must have an Ingress controller in order for this to work.

> **Tip**: You can inspect available Ingress classes in your cluster using:
> `$ kubectl get ingressclasses`
We recommend using the Kubernetes open source community version of
[the nginx ingress](https://kubernetes.github.io/ingress-nginx/). You can install it as follows:

```shell
$ helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
```

...and don't forget to set the `ingress.className` to `nginx` in your `values.yaml`.


### Setting up a TLS Certificate(s) for HTTPS Traffic

HTTPS traffic is served on port 443 (a.k.a. the "SSL" port), and requires the ingress to have
access to a TLS certificate and private key. A certificate signed by a trusted Certificate
Authority is needed for public servers, or you can create your own self-signed certificate for
development purposes - see
[Appendix 1](#appendix-1-self-signing-tls-certificates-for-https-traffic) below, for self-signing
instructions.

Once you have obtained the server certificate and private key, you can add them to the
Kubernetes secrets, as follows (creates a Secret named `tls-secret`, assuming the server
certificate and private key are named `server.crt` and `server.key`):

```shell
kubectl create secret tls tls-secret --key server.key --cert server.crt
# (don't forget to define a non-default namespace if necessary, using `-n myNameSpace`)
```

Then simply tell the ingress which secret to use:

```yaml
ingress:
tls:
- hosts:
# hostname is auto-populated from the value of
# metacat:
# server.name: &extHostname myHostName.com
- *extHostname
secretName: tls-secret
```
### Setting up Certificates for DataONE Replication
DataONE Replication relies on mutual authentication with x509 client-side certs. For full details
on becoming part of the DataONE network, see the
[Metacat Administrator's Guide](https://knb.ecoinformatics.org/knb/docs/dataone.html) and
[Authentication and Authorization in DataONE](https://releases.dataone.org/online/api-documentation-v2.0.1/design/AuthorizationAndAuthentication.html).
To set up the certificates for each Metacat Member Node (MN) or Coordinating Node (CN):
1. First make sure you have the Kubernetes version of the
[nginx ingress installed](#networking-and-x509-certificates)
1. Ensure [HTTPS access is set up](#setting-up-a-tls-certificates-for-https-traffic) and
working correctly
1. From the DataONE administrators, obtain:
1. a copy of the DataONE *Certificate Authority (CA) certificate*, in order to validate client
certificates signed by that authority.
1. a *client certificate*, that uniquely identifies your Metacat instance.
1. Create the Kubernetes Secret (named `ca-secret`) to hold the ca cert (assuming it's in a file named
`DataONECAChain.crt`):

```shell
kubectl create secret generic ca-secret --from-file=ca.crt=DataONECAChain.crt
# (don't forget to define a non-default namespace if necessary, using `-n myNameSpace`)
```

1. Set the correct parameters in `values.yaml`:

```yaml
metacat:
dataone.certificate.fromHttpHeader.enabled: true
ingress:
className: "nginx"
d1CaCertSecretName: ca-secret
```

---

## Appendices

## Appendix 1: Self-Signing TLS Certificates for HTTPS Traffic

Also see the [Kubernetes nginx documentation](https://kubernetes.github.
io/ingress-nginx/user-guide/tls)

> **NOTE: For development and testing purposes only!**

You can create your own self-signed certificate as follows:

```shell
HOST=myHostName.com \
&& openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 -keyout server.key \
-out server.crt \
-subj "/CN=${HOST}/O=${HOST}" \
-addext "subjectAltName = DNS:${HOST}"
```

The output will be a server certificate file named `server.crt`, and a private key file named
`server.key`. For the `${HOST}`, you can use `localhost`, or your machine's real hostname.

Alternatively, you can use any other valid hostname, but you'll need to add an entry to your
`/etc/hosts` file to map it to your localhost IP address (`127.0.0.1`) so that your browser can
resolve it; e.g.:

```shell
# add entry in /etc/hosts
127.0.0.1 myHostName.com
```

Whatever hostname you are using, don't forget to set the
`metacat.server.name` accordingly, in `values.yaml`!

## Appendix 2: Self-Signing Certificates for Testing Mutual Authentication

Also see the
[Kubernetes nginx documentation](https://kubernetes.github.io/ingress-nginx/examples/PREREQUISITES/#client-certificate-authentication)

> **NOTE: For development and testing purposes only!**

You can create your own self-signed certificate as follows:

1. Generate the CA Key and Certificate:

```shell
openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 356 -nodes \
-subj '/CN=My Cert Authority'
```

1. Generate the Server Key, and Certificate and Sign with the CA Certificate:

```shell
openssl req -new -newkey rsa:4096 -keyout server.key -out server.csr -nodes \
-subj '/CN=mydomain.com'
openssl x509 -req -sha256 -days 365 -in server.csr -CA ca.crt -CAkey ca.key \
-set_serial 01 -out server.crt
```

1. Generate the Client Key, and Certificate and Sign with the CA Certificate:

```shell
openssl req -new -newkey rsa:4096 -keyout client.key -out client.csr -nodes \
-subj '/CN=My Client'
openssl x509 -req -sha256 -days 365 -in client.csr -CA ca.crt -CAkey ca.key \
-set_serial 02 -out client.crt
```
Loading

0 comments on commit 6568b06

Please sign in to comment.