AWS EKS Deployment
This article dives into how you might alter your Bitwarden self-hosted Helm Chart deployment based on the specific offerings of AWS and Elastic Kubernetes Service (EKS).
Note that certain add-ons documented in this article will require that your EKS cluster has at least one node already launched.
Requirements
Before proceeding with the installation, ensure the following requirements are met:
- kubectl is installed.
- Helm 3 is installed.
- You have an SSL certificate and key or access to creating one via a certificate provider.
- You have a SMTP server or access to a cloud SMTP provider.
- A storage class that supports ReadWriteMany.
- You have an installation id and key retrieved from https://bitwarden.com/host.
Rootless requirements
Bitwarden will detect whether your environment restricts what user containers can be run as during startup and will automatically initiate deployment in rootless mode if restriction is detected. Successfully deploying in rootless mode requires one of the following two options:
- Deploying an external MSSQL database instead of the SQL container included by default in the Helm chart.
- Assigning elevated privileges to the included SQL container using a service account, pod security context, or other method.
[!TIP] SQL pod as root to non-root While Microsoft requires that SQL containers be run as root, container startup will step down to a non-root user before executing application code.
Ingress controller
Ingress controller and Gateway API configurations have been defined in my-values.yaml, and will require an AWS Network Load Balancer. AWS Application Load Balancers (ALB) are not currently recommended as they do not support path rewrites and path-based routing.
[!TIP] Assumption about NLB for EC2 The following assumes that you have an SSL certificate saved in AWS Certificate Manager, as you will need a certificate Amazon Resource Name (ARN).
You also must have at least 1 node already running in your cluster.
To connect a Network Load Balancer to your cluster:
- Follow these instructions to create an IAM policy and role, and to install the AWS Load Balancer Controller in your cluster.
- Ingress NGINX has reach EOL and will no longer receive support (see the Kubernetes official statement). Bitwarden and AWS recommend migration to an alternative such as Gateway API or third-party ingress controllers.
my-values.yamlwill contain configuration variables for Ingress or Gateway API setups. It is not recommended to configure both simultaneously.
Create a storage class
Deployment requires a shared storage class that you provide, which must support ReadWriteMany. The following example of how to create a storage class that meets the requirement:
[!TIP] AWS Helm EFS assumption The following assumes that you have an AWS Elastic File System (EFS) created. If you don't create one now. In either case, take note of your EFS' File system ID as you will need it during this process.
- Get the Amazon EFS CSI driver add-on for your EKS cluster. This will require that you create an OIDC provider for your cluster and create an IAM role for the driver.
- In the AWS CloudShell, replace the
file_system_id= "REPLACE"variable in the the following script and run it in the AWS CloudShell:
[!WARNING] It's just an example The following is an illustrative example, be sure to assign permissions according to your own security requirements.
file_system_id="REPLACE"
cat << EOF | kubectl apply -n bitwarden -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: shared-storage
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: $file_system_id
directoryPerms: "777" # Change for your use case
uid: "2000" # Change for your use case
gid: "2000" # Change for your use case
basePath: "/dyn1"
subPathPattern: "\${.PVC.name}"
ensureUniqueDirectory: "false"
reuseAccessPoint: "false"
mountOptions:
- iam
- tls
EOF
- Set the
sharedStorageClassNamevalue inmy-values.yamlto whatever name you give the class inmetadata.name:, in this example:
sharedStorageClassName: "shared-storage"
Using AWS Secrets Manager
Deployment requires Kubernetes secrets objects to set sensitive values for your deployment. While the kubectl create secret command can be used to set secrets, AWS customers may prefer to use AWS Secrets Manager and the AWS Secrets and Configuration Provider (ACSP) for Kubernetes Secrets Store CSI Driver.
You will need the following secrets stored in AWS Secrets Manager. Note that you can change the Keys used here but must also make changes to subsequent steps if you do:
| Key | Value |
|---|---|
installationid |
A valid installation id retrieved from https://bitwarden.com/host. For more information, see what are my installation id and installation key used for? |
installationkey |
A valid installation key retrieved from https://bitwarden.com/host. For more information, see what are my installation id and installation key used for? |
smtpusername |
A valid username for your SMTP server. |
smtppassword |
A valid password for the entered SMTP server username. |
yubicoclientid |
Client ID for YubiCloud Validation Service or self-hosted Yubico Validation Server. If YubiCloud, get your client ID and secret key here. |
yubicokey |
Secret key for YubiCloud Validation Service or self-hosted Yubico Validation Server. If YubiCloud, get your client ID and secret key here. |
globalSettings__hibpApiKey |
Your HaveIBeenPwned (HIBP) API Key, available here. This key allows users to run the Data Breach report and to check their master password for presence in breaches when they create an account. |
If you're using the Bitwarden SQL pod, sapassword. If you're using your own SQL server, dbconnectionString. |
Credentials for the database connected to your Bitwarden instance. What is required will depend on whether you're using the included SQL pod or an external SQL server. |
-
Once your secrets are securely stored, install ACSP. During ACSP installation you will:
-
Install the Secrets Store CSI Driver (
secrets-store-driver-csi).
[!NOTE] Secrets Store CSI Driver When installing and configuring the Secrets Store CSI Driver, you must enable
syncSecret.enabled=true. - Install the AWS provider for Secrets Store CSI Driver (secrets-store-driver-csi-provider-aws). 2. Create a permissions policy to allow access to your secrets. This policy must grantsecretsmanager:GetSecretValueandsecretsmanager:DescribeSecretpermission, for example:
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:REPLACEME:REPLACEME:secret:REPLACEME"
}
}
- Create a service account that has access to your secrets via the created permissions policy, for example:
CLUSTER_NAME="REPLACE"
ACCOUNT_ID="REPLACE" # replace with your AWS account ID
ROLE_NAME="REPLACE" # name of a role that will be created in IAM
POLICY_NAME="REPLACE" # the name of the policy you created earlier
eksctl create iamserviceaccount \
--cluster=$CLUSTER_NAME \
--namespace=bitwarden \
--name=bitwarden-sa \
--role-name $ROLE_NAME \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/$POLICY_NAME \
--approve
-
Next, create a SecretProviderClass, as in the following example. Be sure to:
-
Replace the
regionwith your region. - Replace the
objectNamewith the name of the Secrets Manager secret you created (Step 1). - If you're using IRSA, use the same
namespaceas your EKS pods.
cat <<EOF | kubectl apply -n bitwarden -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: bitwarden-secrets-manager-csi
namespace: REPLACE #If using IRSA, specify the namespace where pods are deployed
labels:
app.kubernetes.io/component: secrets
annotations:
spec:
provider: aws
parameters:
region: REPLACE
objects: |
- objectName: "REPLACE"
objectType: "secretsmanager"
objectVersionLabel: "AWSCURRENT"
jmesPath:
- path: installationid
objectAlias: installationid
- path: installationkey
objectAlias: installationkey
- path: smtpusername
objectAlias: smtpusername
- path: smtppassword
objectAlias: smtppassword
- path: yubicoclientid
objectAlias: yubicoclientid
- path: yubicokey
objectAlias: yubicokey
- path: hibpapikey
objectAlias: hibpapikey
- path: sapassword #-OR- dbconnectionstring if external SQL
objectAlias: sapassword #-OR- dbconnectionstring if external SQL
secretObjects:
- secretName: "bitwarden-secret"
type: Opaque
data:
- objectName: installationid
key: globalSettings__installation__id
- objectName: installationkey
key: globalSettings__installation__key
- objectName: smtpusername
key: globalSettings__mail__smtp__username
- objectName: smtppassword
key: globalSettings__mail__smtp__password
- objectName: yubicoclientid
key: globalSettings__yubico__clientId
- objectName: yubicokey
key: globalSettings__yubico__key
- objectName: hibpapikey
key: globalSettings__hibpApiKey
- objectName: sapassword #-OR- dbconnectionstring if external SQL
key: SA_PASSWORD #-OR- globalSettings__sqlServer__connectionString if external SQL
EOF
-
In your
my-values.yamlfile, set the following values: -
secrets.secretName: Set to thesecretNamedefined in your SecretProviderClass (Step 4). secrets.secretProviderClass: Set to themetedata.namedefined in your SecretProviderClass (Step 4).component.admin.podServiceAccount: Set to the name defined for your service account (Step 3).component.api.podServiceAccount: Set to the name defined for your service account (Step 3).component.attachments.podServiceAccount: Set to the name defined for your service account (Step 3).component.events.podServiceAccount: Set to the name defined for your service account (Step 3).component.icons.podServiceAccount: Set to the name defined for your service account (Step 3).component.identity.podServiceAccount: Set to the name defined for your service account (Step 3).component.notifications.podServiceAccount: Set to the name defined for your service account (Step 3).component.scim.podServiceAccount: Set to the name defined for your service account (Step 3).component.sso.podServiceAccount: Set to the name defined for your service account (Step 3).component.web.podServiceAccount: Set to the name defined for your service account (Step 3).database.podServiceAccount: Set to the name defined for your service account (Step 3).serviceAccount.name: Set to the name defined for your service account (Step 3).serviceAccount.deployRolesOnly: Set totrue.
