Skip to content

Security and Authz

Andrew Azores edited this page Mar 16, 2020 · 31 revisions

General Application Architecture w.r.t Security

The application as a whole consists of:

  • Application Pod
    • ContainerJFR container instance
      • Service + Route
    • (optional) Grafana container instance
      • Service + Route
    • (optional) jfr-datasource container instance
      • Service - no route, only accessible from within the cluster
    • PersistentVolumeClaim
  • Operator Pod
    • container-jfr-operator instance

The Routes on the Application Pod are configured with TLS Termination, so all connections from outside the cluster use HTTPS/WSS using the OpenShift cluster's SSL cert.

Flow of JFR Data

ContainerJFR connects to other JVM applications within its cluster using remote JMX, using cluster-internal URLs so that no traffic will potentially leave the cluster. These JMX connections do not currently support JMX connection security (auth credentials or SSL), but the connections are always hidden within OpenShift cluster networking as a firewall.

Once ContainerJFR has established a JMX connection to a target application its primary purpose is to enable JFR recordings on the target JVM and expose them to the end user. These recordings can be transferred from the target JVM back to ContainerJFR over the JMX connection. ContainerJFR does this for four purposes:

  1. to generate JMC HTML Rules Reports of the JFR contents, which are served to clients over HTTPS
  2. to copy the JFR contents into a file saved in its OpenShift PersistentVolumeClaim ("archive")
  3. to stream a snapshot of the JFR contents over HTTPS to a requesting client's GET request
  4. to upload a snapshot of the JFR contents using HTTPS POST to the jfr-datasource

("archived" JFR copies can also be streamed to clients over HTTPS or POSTed to jfr-datasource, and HTML Rules Reports can also be made of them)

ContainerJFR Authz Specifics

When deployed in OpenShift, the ContainerJFR container instance detects this scenario and expects clients to provide a Bearer token on all Command Channel (WSS) connections as well as on any HTTPS API connections that can provide information about target applications within the cluster (ie authz are not checked only for requests for things like web-client assets). These tokens are the ones provided by OpenShift OAuth itself, ie. the user's account for that OpenShift instance/cluster. On each HTTPS request, ContainerJFR receives the token and send its own request to the internal OpenShift OAuth server to validate the token. If OpenShift OAuth validates the token the request is accepted. If OpenShift OAuth does not validate the token, or the user does not provide a token, then the request is rejected with a 401. Likewise, for each new WSS WebSocket connection, ContainerJFR expects the client to provide a token as part of the WebSocket SubProtocol header. This token is then passed to the OpenShift OAuth server in the same way previously described. If the token validation fails, the server will reply with an appropriate closure status code and message after the client sends its first message frame.

TODO describe non-OpenShift cases

Grafana Authz

The Operator configures the Grafana container to use the default admin username, but the default password is overridden. The Operator generates a random password as below (at the time of writing):

func NewGrafanaSecretForCR(cr *rhjmcv1alpha1.ContainerJFR) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cr.Name + "-grafana-basic",
			Namespace: cr.Namespace,
		},
		StringData: map[string]string{
			"GF_SECURITY_ADMIN_USER":     "admin",
			"GF_SECURITY_ADMIN_PASSWORD": GenPasswd(20),
		},
	}
}

func GenPasswd(length int) string {
	rand.Seed(time.Now().UnixNano())
	chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
	b := make([]byte, length)
	for i := range b {
		b[i] = chars[rand.Intn(len(chars))]
	}
	return string(b)
}

(ie: [a-zA-Z0-9\-_]{20})

This generated password is stored in a Kubernetes Secret, which is then "mounted" into the Grafana container as an environment variable at startup time. This Secret is also re-read by another controller within the Operator at a later time after Grafana container startup so that the Operator can perform API requests to the Grafana container to configure it with a default dashboard and to add the jfr-datasource datasource definition/URL.

Operator Connections to ContainerJFR

TODO

Clone this wiki locally