lifecycle: move AWS CFN generation to lifecycle and fix CI (#12743)
* fix missing min_healthy_percent which was causing an error on stdout...sigh Signed-off-by: Jens Langhammer <jens@goauthentik.io> * disable version reporting (replaces deleting BootstrapVersion) Signed-off-by: Jens Langhammer <jens@goauthentik.io> * dont generate bootstrap thing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * aaand remove fix_template Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always set CI to false so errors are sent to stderr Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move aws stuff to lifecycle Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix missing package-lock Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix package Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup website structure Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -31,4 +31,4 @@ The stack will output the endpoint of the ALB that to which you can point your D
|
||||
|
||||
### Further customization
|
||||
|
||||
If you require further customization, we recommend you install authentik via [Docker Compose](../docker-compose.mdx) or [Kubernetes](../kubernetes.md).
|
||||
If you require further customization, we recommend you install authentik via [Docker Compose](./docker-compose.mdx) or [Kubernetes](./kubernetes.md).
|
@ -1 +0,0 @@
|
||||
cdk.out
|
@ -1,421 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
|
||||
from aws_cdk import (
|
||||
App,
|
||||
CfnOutput,
|
||||
CfnParameter,
|
||||
Duration,
|
||||
RemovalPolicy,
|
||||
Stack,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_ec2 as ec2,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_ecs as ecs,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_efs as efs,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_elasticache as elasticache,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_elasticloadbalancingv2 as elbv2,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_rds as rds,
|
||||
)
|
||||
from aws_cdk import (
|
||||
aws_secretsmanager as secretsmanager,
|
||||
)
|
||||
from constructs import Construct
|
||||
|
||||
from authentik import __version__
|
||||
|
||||
|
||||
class AuthentikStack(Stack):
|
||||
def __init__(self, scope: Construct, id: str, **kwargs):
|
||||
super().__init__(scope, id, *kwargs)
|
||||
|
||||
### Inputs
|
||||
|
||||
db_instance_type = CfnParameter(
|
||||
self,
|
||||
"DBInstanceType",
|
||||
type="String",
|
||||
default="m5.large",
|
||||
description="RDS PostgreSQL instance type (without the leading db.)",
|
||||
)
|
||||
db_version = CfnParameter(
|
||||
self, "DBVersion", type="String", default="17.1", description="RDS PostgreSQL version"
|
||||
)
|
||||
db_storage = CfnParameter(
|
||||
self,
|
||||
"DBStorage",
|
||||
type="Number",
|
||||
default=10,
|
||||
min_value=10,
|
||||
description="RDS PostgreSQL storage size in GB",
|
||||
)
|
||||
|
||||
redis_instance_type = CfnParameter(
|
||||
self,
|
||||
"RedisInstanceType",
|
||||
type="String",
|
||||
default="cache.t4g.medium",
|
||||
description="ElastiCache Redis instance type (with the leading cache.)",
|
||||
)
|
||||
redis_version = CfnParameter(
|
||||
self,
|
||||
"RedisVersion",
|
||||
type="String",
|
||||
default="7.1",
|
||||
description="ElastiCache Redis version",
|
||||
)
|
||||
|
||||
authentik_image = CfnParameter(
|
||||
self,
|
||||
"AuthentikImage",
|
||||
type="String",
|
||||
default="ghcr.io/goauthentik/server",
|
||||
description="authentik Docker image",
|
||||
)
|
||||
authentik_version = CfnParameter(
|
||||
self,
|
||||
"AuthentikVersion",
|
||||
type="String",
|
||||
default=__version__,
|
||||
description="authentik Docker image tag",
|
||||
)
|
||||
|
||||
server_cpu = CfnParameter(
|
||||
self,
|
||||
"AuthentikServerCPU",
|
||||
type="Number",
|
||||
default=512,
|
||||
description="authentik server CPU units (1024 = 1 vCPU)",
|
||||
)
|
||||
server_memory = CfnParameter(
|
||||
self,
|
||||
"AuthentikServerMemory",
|
||||
type="Number",
|
||||
default=1024,
|
||||
description="authentik server memory in MiB",
|
||||
)
|
||||
server_desired_count = CfnParameter(
|
||||
self,
|
||||
"AuthentikServerDesiredCount",
|
||||
type="Number",
|
||||
default=2,
|
||||
min_value=1,
|
||||
description="Desired number of authentik server tasks",
|
||||
)
|
||||
|
||||
worker_cpu = CfnParameter(
|
||||
self,
|
||||
"AuthentikWorkerCPU",
|
||||
type="Number",
|
||||
default=512,
|
||||
description="authentik worker CPU units (1024 = 1 vCPU)",
|
||||
)
|
||||
worker_memory = CfnParameter(
|
||||
self,
|
||||
"AuthentikWorkerMemory",
|
||||
type="Number",
|
||||
default=1024,
|
||||
description="authentik worker memory in MiB",
|
||||
)
|
||||
worker_desired_count = CfnParameter(
|
||||
self,
|
||||
"AuthentikWorkerDesiredCount",
|
||||
type="Number",
|
||||
default=2,
|
||||
min_value=1,
|
||||
description="Desired number of authentik worker tasks",
|
||||
)
|
||||
|
||||
certificate_arn = CfnParameter(
|
||||
self,
|
||||
"CertificateARN",
|
||||
type="String",
|
||||
description="ACM certificate ARN for HTTPS access",
|
||||
)
|
||||
|
||||
### Resources
|
||||
|
||||
# VPC
|
||||
|
||||
vpc = ec2.Vpc(self, "AuthentikVpc", max_azs=2, nat_gateways=1)
|
||||
|
||||
# Security Groups
|
||||
|
||||
db_security_group = ec2.SecurityGroup(
|
||||
self, "DatabaseSG", vpc=vpc, description="Security Group for authentik RDS PostgreSQL"
|
||||
)
|
||||
redis_security_group = ec2.SecurityGroup(
|
||||
self, "RedisSG", vpc=vpc, description="Security Group for authentik ElastiCache Redis"
|
||||
)
|
||||
authentik_security_group = ec2.SecurityGroup(
|
||||
self, "AuthentikSG", vpc=vpc, description="Security Group for authentik services"
|
||||
)
|
||||
db_security_group.add_ingress_rule(
|
||||
peer=authentik_security_group,
|
||||
connection=ec2.Port.tcp(5432),
|
||||
description="Allow authentik to connect to RDS PostgreSQL",
|
||||
)
|
||||
redis_security_group.add_ingress_rule(
|
||||
peer=authentik_security_group,
|
||||
connection=ec2.Port.tcp(6379),
|
||||
description="Allow authentik to connect to ElastiCache Redis",
|
||||
)
|
||||
|
||||
# Generated secrets
|
||||
|
||||
db_password = secretsmanager.Secret(
|
||||
self,
|
||||
"DBPassword",
|
||||
generate_secret_string=secretsmanager.SecretStringGenerator(
|
||||
secret_string_template=json.dumps({"username": "authentik"}),
|
||||
generate_string_key="password",
|
||||
password_length=64,
|
||||
exclude_characters='"@/\\',
|
||||
),
|
||||
)
|
||||
secret_key = secretsmanager.Secret(
|
||||
self,
|
||||
"AuthentikSecretKey",
|
||||
generate_secret_string=secretsmanager.SecretStringGenerator(
|
||||
password_length=64, exclude_characters='"@/\\'
|
||||
),
|
||||
)
|
||||
|
||||
# Database
|
||||
|
||||
database = rds.DatabaseInstance(
|
||||
self,
|
||||
"AuthentikDB",
|
||||
engine=rds.DatabaseInstanceEngine.postgres(
|
||||
version=rds.PostgresEngineVersion.of(db_version.value_as_string, ""),
|
||||
),
|
||||
instance_type=ec2.InstanceType(db_instance_type.value_as_string),
|
||||
vpc=vpc,
|
||||
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
|
||||
allocated_storage=db_storage.value_as_number,
|
||||
security_groups=[db_security_group],
|
||||
database_name="authentik",
|
||||
credentials=rds.Credentials.from_secret(db_password),
|
||||
multi_az=True,
|
||||
removal_policy=RemovalPolicy.SNAPSHOT,
|
||||
)
|
||||
|
||||
# Redis
|
||||
|
||||
redis_subnet_group = elasticache.CfnSubnetGroup(
|
||||
self,
|
||||
"AuthentikRedisSubnetGroup",
|
||||
subnet_ids=vpc.select_subnets(
|
||||
subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS
|
||||
).subnet_ids,
|
||||
description="Subnet group for authentik ElastiCache Redis",
|
||||
)
|
||||
|
||||
redis = elasticache.CfnReplicationGroup(
|
||||
self,
|
||||
"AuthentikRedis",
|
||||
replication_group_description="Redis cluster for authentik",
|
||||
engine="redis",
|
||||
engine_version=redis_version.value_as_string,
|
||||
cache_node_type=redis_instance_type.value_as_string,
|
||||
num_cache_clusters=2,
|
||||
automatic_failover_enabled=True,
|
||||
security_group_ids=[redis_security_group.security_group_id],
|
||||
cache_subnet_group_name=redis_subnet_group.ref,
|
||||
)
|
||||
|
||||
# Storage
|
||||
|
||||
media_fs = efs.FileSystem(
|
||||
self,
|
||||
"AuthentikMediaEFS",
|
||||
vpc=vpc,
|
||||
removal_policy=RemovalPolicy.RETAIN,
|
||||
security_group=ec2.SecurityGroup(
|
||||
self,
|
||||
"AuthentikMediaEFSSecurityGroup",
|
||||
vpc=vpc,
|
||||
description="Security group for authentik media EFS",
|
||||
allow_all_outbound=True,
|
||||
),
|
||||
encrypted=True,
|
||||
performance_mode=efs.PerformanceMode.GENERAL_PURPOSE,
|
||||
throughput_mode=efs.ThroughputMode.BURSTING,
|
||||
)
|
||||
media_fs.connections.allow_default_port_from(authentik_security_group)
|
||||
|
||||
media_access_point = media_fs.add_access_point(
|
||||
"AuthentikMediaAccessPoint",
|
||||
path="/media",
|
||||
create_acl=efs.Acl(owner_uid="1000", owner_gid="1000", permissions="755"),
|
||||
posix_user=efs.PosixUser(uid="1000", gid="1000"),
|
||||
)
|
||||
|
||||
# ECS Cluster
|
||||
|
||||
cluster = ecs.Cluster(self, "AuthentikCluster", vpc=vpc)
|
||||
|
||||
environment = {
|
||||
"AUTHENTIK_POSTGRESQL__HOST": database.instance_endpoint.hostname,
|
||||
"AUTHENTIK_POSTGRESQL__USER": "authentik",
|
||||
"AUTHENTIK_REDIS__HOST": redis.attr_primary_end_point_address,
|
||||
}
|
||||
|
||||
secrets = {
|
||||
"AUTHENTIK_POSTGRESQL__PASSWORD": ecs.Secret.from_secrets_manager(
|
||||
db_password, field="password"
|
||||
),
|
||||
"AUTHENTIK_SECRET_KEY": ecs.Secret.from_secrets_manager(secret_key),
|
||||
}
|
||||
|
||||
server_task = ecs.FargateTaskDefinition(
|
||||
self,
|
||||
"AuthentikServerTask",
|
||||
cpu=server_cpu.value_as_number,
|
||||
memory_limit_mib=server_memory.value_as_number,
|
||||
)
|
||||
server_task.add_volume(
|
||||
name="media",
|
||||
efs_volume_configuration=ecs.EfsVolumeConfiguration(
|
||||
file_system_id=media_fs.file_system_id,
|
||||
transit_encryption="ENABLED",
|
||||
authorization_config=ecs.AuthorizationConfig(
|
||||
access_point_id=media_access_point.access_point_id,
|
||||
iam="ENABLED",
|
||||
),
|
||||
),
|
||||
)
|
||||
server_container = server_task.add_container(
|
||||
"AuthentikServerContainer",
|
||||
image=ecs.ContainerImage.from_registry(
|
||||
f"{authentik_image.value_as_string}:{authentik_version.value_as_string}"
|
||||
),
|
||||
command=["server"],
|
||||
environment=environment,
|
||||
secrets=secrets,
|
||||
logging=ecs.LogDriver.aws_logs(stream_prefix="authentik-server"),
|
||||
enable_restart_policy=True,
|
||||
health_check=ecs.HealthCheck(
|
||||
command=["CMD", "ak", "healthcheck"],
|
||||
interval=Duration.seconds(30),
|
||||
retries=3,
|
||||
start_period=Duration.seconds(60),
|
||||
timeout=Duration.seconds(30),
|
||||
),
|
||||
)
|
||||
server_container.add_port_mappings(ecs.PortMapping(container_port=9000))
|
||||
server_container.add_mount_points(
|
||||
ecs.MountPoint(container_path="/media", source_volume="media", read_only=False)
|
||||
)
|
||||
server_service = ecs.FargateService(
|
||||
self,
|
||||
"AuthentikServerService",
|
||||
cluster=cluster,
|
||||
task_definition=server_task,
|
||||
desired_count=server_desired_count.value_as_number,
|
||||
security_groups=[authentik_security_group],
|
||||
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
|
||||
enable_execute_command=True,
|
||||
)
|
||||
|
||||
worker_task = ecs.FargateTaskDefinition(
|
||||
self,
|
||||
"AuthentikWorkerTask",
|
||||
cpu=worker_cpu.value_as_number,
|
||||
memory_limit_mib=worker_memory.value_as_number,
|
||||
)
|
||||
worker_task.add_volume(
|
||||
name="media",
|
||||
efs_volume_configuration=ecs.EfsVolumeConfiguration(
|
||||
file_system_id=media_fs.file_system_id,
|
||||
transit_encryption="ENABLED",
|
||||
authorization_config=ecs.AuthorizationConfig(
|
||||
access_point_id=media_access_point.access_point_id,
|
||||
iam="ENABLED",
|
||||
),
|
||||
),
|
||||
)
|
||||
worker_container = worker_task.add_container(
|
||||
"AuthentikWorkerContainer",
|
||||
image=ecs.ContainerImage.from_registry(
|
||||
f"{authentik_image.value_as_string}:{authentik_version.value_as_string}"
|
||||
),
|
||||
command=["worker"],
|
||||
environment=environment,
|
||||
secrets=secrets,
|
||||
logging=ecs.LogDriver.aws_logs(stream_prefix="authentik-worker"),
|
||||
enable_restart_policy=True,
|
||||
health_check=ecs.HealthCheck(
|
||||
command=["CMD", "ak", "healthcheck"],
|
||||
interval=Duration.seconds(30),
|
||||
retries=3,
|
||||
start_period=Duration.seconds(60),
|
||||
timeout=Duration.seconds(30),
|
||||
),
|
||||
)
|
||||
worker_container.add_mount_points(
|
||||
ecs.MountPoint(container_path="/media", source_volume="media", read_only=False)
|
||||
)
|
||||
worker_service = ecs.FargateService( # noqa: F841
|
||||
self,
|
||||
"AuthentikWorkerService",
|
||||
cluster=cluster,
|
||||
task_definition=worker_task,
|
||||
desired_count=worker_desired_count.value_as_number,
|
||||
security_groups=[authentik_security_group],
|
||||
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
|
||||
enable_execute_command=True,
|
||||
)
|
||||
|
||||
# Load balancer
|
||||
|
||||
lb = elbv2.ApplicationLoadBalancer(
|
||||
self,
|
||||
"AuthentikALB",
|
||||
vpc=vpc,
|
||||
internet_facing=True,
|
||||
)
|
||||
https_redirect = lb.add_listener( # noqa: F841
|
||||
"AuthentikHttpListener",
|
||||
port=80,
|
||||
default_action=elbv2.ListenerAction.redirect(permanent=True, protocol="HTTPS"),
|
||||
)
|
||||
listener = lb.add_listener(
|
||||
"AuthentikHttpsListener",
|
||||
port=443,
|
||||
certificates=[
|
||||
elbv2.ListenerCertificate(certificate_arn=certificate_arn.value_as_string)
|
||||
],
|
||||
)
|
||||
target_group = listener.add_targets( # noqa: F841
|
||||
"AuthentikServerTarget",
|
||||
protocol=elbv2.ApplicationProtocol.HTTP,
|
||||
port=9000,
|
||||
targets=[server_service],
|
||||
health_check=elbv2.HealthCheck(
|
||||
path="/-/health/live/",
|
||||
healthy_http_codes="200",
|
||||
),
|
||||
)
|
||||
|
||||
CfnOutput(
|
||||
self,
|
||||
"LoadBalancerDNS",
|
||||
value=lb.load_balancer_dns_name,
|
||||
)
|
||||
|
||||
|
||||
app = App()
|
||||
AuthentikStack(app, "AuthentikStack")
|
||||
app.synth()
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"app": "./app.py"
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import yaml
|
||||
|
||||
with open("template.yaml") as file:
|
||||
template = yaml.safe_load(file)
|
||||
del template["Conditions"]["CDKMetadataAvailable"]
|
||||
del template["Parameters"]["BootstrapVersion"]
|
||||
del template["Resources"]["CDKMetadata"]
|
||||
with open("template.yaml", "w") as file:
|
||||
yaml.dump(template, file)
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user