Security
Passwords
Artemis uses configuration files that contain default passwords and secrets. These must be overridden in your own configuration or via environment variables. The Spring Relaxed Binding documentation shows how to translate YAML keys into the corresponding environment variable names.
artemis:
user-management:
internal-admin:
username: "artemis-admin"
password: "artemis-admin"
version-control:
# Only for Jenkins with LocalVC, i.e. a node WITHOUT local CI. A node running local CI refuses to start with
# either of these set; its build agents authenticate per build job. See "Build Agent Authentication" below.
# build-agent-git-username: "buildagent_user"
# build-agent-git-password: "buildagent_password"
jhipster:
security:
authentication:
jwt:
base64-secret: ""
registry:
password: "change-me"
Startup Validation
Under the prod profile, Artemis refuses to start when one of these properties still holds a value that is published in this repository: the JWT signing key, the internal admin password (this includes a password identical to the username), and the build-agent git password. The startup error names the property and how to supply a value.
The build-agent git password is exempt when artemis.version-control.build-agent-use-ssh is true, because the core nodes then stop granting that credential pair read access to every repository, which is the access this check exists to protect. The pair is not rejected everywhere: it still runs through ordinary Basic authentication afterwards, where it opens exactly what the named account may access - nothing at all for buildjob_user, which is not an Artemis account. The check runs on every startup, so it applies again on the first start after the property goes back to false.
The signing key is checked through both jhipster.security.authentication.jwt.base64-secret and the plain jhipster.security.authentication.jwt.secret, because either one can be the key that is actually used. It is also rejected when it is absent altogether, and when it decodes to fewer than 64 bytes, which is the minimum HS512 accepts. The comparison happens on the decoded key as well as on the configured spelling, so re-encoding a published value - different padding, an added line break - does not get past it. Generate a key with:
openssl rand -base64 64 | tr -d '\n'
docker/artemis/config/prod.env therefore keeps placeholders that this validation rejects: a deployment that has not been configured yet fails immediately instead of running on values that anyone can read. Supply your own values from outside version control — an env file of your own added through a compose override file, or ARTEMIS_ENV_FILE for the docker/test-server-*.yml compose files, which replaces prod.env entirely (see Docker Compose Setup):
JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64SECRET="<output of the command above>"
ARTEMIS_USERMANAGEMENT_INTERNALADMIN_PASSWORD="<a unique password>"
ARTEMIS_VERSIONCONTROL_BUILDAGENTGITPASSWORD="<a unique password, kept in sync with the build agents>"
Artemis also pins the restrictive rendering profile of the diagram renderer at startup, in every profile. If PLANTUML_SECURITY_PROFILE is set explicitly, it has to name either SANDBOX or ALLOWLIST; any other value is rejected. Artemis passes its diagram theme as inline content, so no Artemis feature depends on the other profiles.
Build Agent Authentication
Build agents clone repositories from the core nodes. A clone is only served when the caller is a known agent, is calling from an address that agent is connected from, and is asking for a repository it currently has a build job for.
Choose in this order. The first two need no secret from you at all — the agents establish their own credentials at startup and per build job — and both are scoped to the build job being run. The third is a shared secret that opens everything, and Artemis refuses to start with it wherever one of the first two can do the work.
| Mechanism | Configuration | Scope | Where allowed |
|---|---|---|---|
| SSH key (recommended) | build-agent-use-ssh: true, ssh-template-clone-url, ssh-private-key-folder-path; the key pair itself is generated automatically | the repositories of the agent's running build jobs | everywhere |
| Build job clone token (automatic fallback over HTTPS) | none | the repositories of one running build job | everywhere |
| Shared git credentials | build-agent-git-username and build-agent-git-password | every repository in the installation | only without local CI (Jenkins with LocalVC) |
What a build agent may read
Both current mechanisms are bounded by the build job the agent is actually running, which Artemis tracks in the distributed processing list:
- A clone token is minted when a build job is queued and travels with it. It is accepted only for the repositories that job declares — the participant's assignment repository, the tests, the solution where the exercise checks it out, and every auxiliary repository — and only from the agent that claimed the job. Auxiliary repositories need no special handling: they are part of the job, so the same token covers them. It has no expiry of its own and needs none: a job leaves the processing list when it finishes, is cancelled, or hits the build timeout, and the token stops working at that moment.
- Over SSH, the public key already establishes which agent is connected, so no token is involved. The same processing list decides which repositories that agent may read.
Both paths write a VCS access log entry naming the agent and the build job, recorded under the mechanism the agent used — BUILD_JOB_TOKEN over HTTPS and SSH with a key — so which one an installation actually uses is visible in the audit UI rather than inferred from its configuration. The old shared-credential shortcut wrote nothing at all, which made the most privileged reader in the installation the only one that could not be audited.
Restricting where build agents may connect from
Every build agent is registered at the address it reaches the core nodes from, and a clone is only served from an address the presenting agent is registered at.
Optionally, bound which hosts may act as a build agent at all:
artemis:
continuous-integration:
build-agent-network:
allowed-ranges:
- "10.0.0.0/8"
trusted-proxies:
- "10.0.0.1"
allowed-rangesaccepts CIDR blocks and single addresses, IPv4 and IPv6. An agent connecting from outside them is refused, which is what stops a host that reached your cluster from registering itself as a build agent. Leaving it empty means no restriction, so upgrading does not stop existing agents.trusted-proxiesnames the reverse proxies whoseX-Forwarded-Forheader may be believed for HTTP git requests. Any client can set that header, so it is only consulted when the request's immediate peer is one of these addresses. Leave it empty when Artemis is reached directly.
SSH behind a load balancer
A load balancer that forwards port 7921 at the TCP level hides the client: every SSH connection would appear to come from the balancer, which would put all SSH users into one rate limit bucket and make the address check above meaningless. Enable PROXY protocol on both sides:
stream {
server {
listen 7921;
proxy_pass 127.0.0.1:7921;
proxy_protocol on;
}
}
artemis:
version-control:
ssh-proxy-protocol:
trusted-sources:
- "10.0.0.1"
A connection from a listed address must begin with a valid PROXY protocol header and is refused otherwise. A connection from any other address is handled as ordinary SSH and keeps its socket peer, so direct connections are unaffected. The list is keyed on the source address rather than on whether a header is present, because believing a header from any sender would let anyone who can reach the port claim an arbitrary client address.
Moving build agents to SSH
artemis.version-control.build-agent-use-ssh does different things on the two node roles. On a build agent it selects the mechanism the agent uses. On a core node it decides whether the node offers SSH host keys for build agents at all. A core node always accepts a registered build agent's public key, whatever the property says there, because a key is per-agent and only reaches a core node through an agent that has joined the cluster — so agents can be moved one at a time while the core nodes still say false.
- Set
build-agent-use-ssh: trueon the build agents, together withssh-template-clone-urlandssh-private-key-folder-path, and restart them. Each generates its own key pair; the core nodes accept it immediately, so builds keep working. - Once no agent clones over HTTPS any more, set the property on the core nodes and restart them.
Database Credentials
The database services used by docker/artemis-prod-postgres.yml and docker/artemis-prod-mysql.yml extend the shared docker/postgres.yml/docker/mysql.yml service definitions, which default to docker/postgres/default.env and docker/mysql/default.env for local development and testing. Those default env files intentionally use no real authentication (Postgres POSTGRES_HOST_AUTH_METHOD=trust, MySQL MYSQL_ALLOW_EMPTY_PASSWORD=yes with an empty root password) so a fresh local setup works without any configuration.
LDAP Authentication
Artemis supports authenticating users against an external LDAP (Lightweight Directory Access Protocol) server. When enabled, users can log in using their institutional credentials, and Artemis will automatically retrieve user information such as name, email, and registration number from the LDAP directory.
How It Works
When a user attempts to log in with LDAP authentication enabled:
- Artemis queries the LDAP server to find the user by their login or email
- The user's password is verified against the LDAP server
- If authentication succeeds, Artemis creates or updates the local user record with information from LDAP
- The user is granted access to Artemis
Configuring LDAP
To enable LDAP authentication, set the module-feature flag and configure the LDAP connection settings in your application-artemis.yml:
Enable LDAP
artemis:
user-management:
ldap:
enabled: true
Configure LDAP Connection
artemis:
user-management:
ldap:
url: ldaps://ldap.example.com:636
user-dn: cn=artemis-service,ou=services,dc=example,dc=com
password: <ldap-service-password>
base: ou=users,dc=example,dc=com
allowed-username-pattern: '^([a-z]{2}\d{2}[a-z]{3})$'
Configuration Options:
artemis.user-management.ldap.url: The URL of your LDAP server (useldaps://for secure connections)artemis.user-management.ldap.user-dn: The distinguished name (DN) of the service account used to bind to the LDAP serverartemis.user-management.ldap.password: The password for the service accountartemis.user-management.ldap.base: The base DN where user searches will be performedartemis.user-management.ldap.allowed-username-pattern: A regex pattern to validate usernames (optional)
Passkey Authentication
Artemis supports passkey-based authentication as more secure authentication method compared to passwords. Passkeys provide phishing-resistant authentication using WebAuthn/FIDO2 standards.
Passkey Approval
When passkeys are activated and passkey enforcement is enabled for administrator access, an additional security layer is implemented:
- Super Admins: Can approve passkey registrations for administrators
- Admins: Require passkey approval from a Super Admin before they can use administrator access
This approach minimizes the attack surface for the critical operation of registering new passkeys by limiting this privilege to a very small number of Super Admin users. This ensures that even if an administrator account is compromised, the attacker cannot bypass passkey enforcement by registering their own passkey without Super Admin approval.
Configuring Passkey Enforcement
To enable passkeys and enforce them for administrator features, add the following to your configuration:
artemis:
user-management:
passkey:
enabled: true
require-for-administrator-features: true
Configuration Options:
artemis.user-management.passkey.enabled: Enables passkey authentication functionality (default:false)artemis.user-management.passkey.require-for-administrator-features: When enabled, administrators must be signed in with an approved passkey before any endpoint grants them administrator access (default:false)
Scope of Enforcement
Enforcement covers the global administrator override, not only the endpoints below /api/.../admin/:
- Administrator endpoints reject the request and return a passkey-required response, which the client turns into a prompt to sign in with a passkey.
- Normal endpoints (courses, exercises, lectures, exams, search, tutorial groups) stop treating the account as an administrator. An administrator who has no approved passkey keeps every role they hold explicitly, so they still see and manage the courses they are an instructor, editor, tutor or student in, and no longer see the courses they hold no role in.
- Monitoring endpoints below
/management/require the same elevation. The public health and info endpoints, and the IP-restricted Prometheus endpoint, are unaffected.
Signing in with an approved passkey restores the override for that session. Sessions established with a password keep working for everything the account is explicitly entitled to.
SSH Access
To allow users to clone their programming exercises via SSH in the integrated code lifecycle setup, SSH must be configured correctly on the server.
Follow the next steps to create and manage SSH key pairs, distribute them across multiple nodes via Ansible, configure the system to use these keys, and adapt Nginx to enable SSH routing.
Generate Key Pairs
ssh-keygen -t rsa -b 4096 -f ~/artemis_ssh/id_rsa
ssh-keygen -t ed25519 -f ~/artemis_ssh/id_ed25519
Make sure the keys have the standard name for the according key type. E.g. id_rsa for RSA.
Distribute Keys via Ansible
You can use the example Ansible playbook below to distribute the keys to the Artemis host. In a multinode setup, ensure all nodes use the same SSH keys to ensure clients can communicate with all nodes.
- name: Distribute SSH keys
hosts: all
vars:
key_dir: "/path/to/keys"
tasks:
- name: Copy RSA key
copy:
src: "{{ key_dir }}/id_rsa"
dest: "~/.ssh/id_rsa"
mode: '0600'
Enable SSH Routing via Nginx
In a multi-node setup you might want to configure the Nginx proxy to also distribute SSH connections to different Artemis instances.
stream {
server {
listen 7921;
proxy_pass 127.0.0.1:7921;
}
}
Rate Limiting
Artemis provides cluster-wide rate limiting powered by Bucket4j (token buckets) and Hazelcast (distributed state). It helps protect authentication and account management endpoints from brute-force attempts and other types of abusive traffic.
What It Does
- Tracks requests per client IP across all nodes in the cluster
- Restricts how frequently sensitive endpoints can be accessed
- Enforces limits for REST authentication, WebAuthn, and Git operations
- Returns HTTP 429 Too Many Requests with
Retry-Afterheader - Allows per-category configuration of requests per minute
- Automatically excludes build agents from rate limiting
Configuring the Rate Limit
artemis:
rate-limiting:
enabled: true
account-management-requests-per-minute: 5
authentication-requests-per-minute: 30
login-options-requests-per-minute: 30
Where It Is Enforced
- Registration
- Account activation
- Password reset (request reset link + actual password change)
- Login options lookup (the identifier step of the login form, in its own bucket so that consuming it does not reduce the number of logins a shared address can perform)
- Username/password login
- WebAuthn authentication
- Git over SSH and HTTP operations
We plan to add the rate-limit to more endpoints (like Oauth2) in the future.