Merge pull request #66274 from talyz/gitlab
nixos/gitlab: Add support for secure secrets and more
This commit is contained in:
commit
2f3b9cd52c
|
@ -24,4 +24,116 @@ pkgs: with pkgs.lib;
|
|||
throw "${shell} is not a shell package"
|
||||
else
|
||||
shell;
|
||||
|
||||
/* Recurse into a list or an attrset, searching for attrs named like
|
||||
the value of the "attr" parameter, and return an attrset where the
|
||||
names are the corresponding jq path where the attrs were found and
|
||||
the values are the values of the attrs.
|
||||
|
||||
Example:
|
||||
recursiveGetAttrWithJqPrefix {
|
||||
example = [
|
||||
{
|
||||
irrelevant = "not interesting";
|
||||
}
|
||||
{
|
||||
ignored = "ignored attr";
|
||||
relevant = {
|
||||
secret = {
|
||||
_secret = "/path/to/secret";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
} "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
|
||||
*/
|
||||
recursiveGetAttrWithJqPrefix = item: attr:
|
||||
let
|
||||
recurse = prefix: item:
|
||||
if item ? ${attr} then
|
||||
nameValuePair prefix item.${attr}
|
||||
else if isAttrs item then
|
||||
map (name: recurse (prefix + "." + name) item.${name}) (attrNames item)
|
||||
else if isList item then
|
||||
imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
|
||||
else
|
||||
[];
|
||||
in listToAttrs (flatten (recurse "" item));
|
||||
|
||||
/* Takes an attrset and a file path and generates a bash snippet that
|
||||
outputs a JSON file at the file path with all instances of
|
||||
|
||||
{ _secret = "/path/to/secret" }
|
||||
|
||||
in the attrset replaced with the contents of the file
|
||||
"/path/to/secret" in the output JSON.
|
||||
|
||||
When a configuration option accepts an attrset that is finally
|
||||
converted to JSON, this makes it possible to let the user define
|
||||
arbitrary secret values.
|
||||
|
||||
Example:
|
||||
If the file "/path/to/secret" contains the string
|
||||
"topsecretpassword1234",
|
||||
|
||||
genJqSecretsReplacementSnippet {
|
||||
example = [
|
||||
{
|
||||
irrelevant = "not interesting";
|
||||
}
|
||||
{
|
||||
ignored = "ignored attr";
|
||||
relevant = {
|
||||
secret = {
|
||||
_secret = "/path/to/secret";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
} "/path/to/output.json"
|
||||
|
||||
would generate a snippet that, when run, outputs the following
|
||||
JSON file at "/path/to/output.json":
|
||||
|
||||
{
|
||||
"example": [
|
||||
{
|
||||
"irrelevant": "not interesting"
|
||||
},
|
||||
{
|
||||
"ignored": "ignored attr",
|
||||
"relevant": {
|
||||
"secret": "topsecretpassword1234"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
|
||||
|
||||
# Like genJqSecretsReplacementSnippet, but allows the name of the
|
||||
# attr which identifies the secret to be changed.
|
||||
genJqSecretsReplacementSnippet' = attr: set: output:
|
||||
let
|
||||
secrets = recursiveGetAttrWithJqPrefix set attr;
|
||||
in ''
|
||||
if [[ -h '${output}' ]]; then
|
||||
rm '${output}'
|
||||
fi
|
||||
''
|
||||
+ concatStringsSep
|
||||
"\n"
|
||||
(imap1 (index: name: "export secret${toString index}=$(<'${secrets.${name}}')")
|
||||
(attrNames secrets))
|
||||
+ "\n"
|
||||
+ "${pkgs.jq}/bin/jq >'${output}' '"
|
||||
+ concatStringsSep
|
||||
" | "
|
||||
(imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
|
||||
(attrNames secrets))
|
||||
+ ''
|
||||
' <<'EOF'
|
||||
${builtins.toJSON set}
|
||||
EOF
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
# TODO: support non-postgresql
|
||||
|
||||
|
@ -12,14 +12,12 @@ let
|
|||
gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
|
||||
gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
|
||||
pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
|
||||
pgSuperUser = config.services.postgresql.superUser;
|
||||
|
||||
databaseConfig = {
|
||||
production = {
|
||||
adapter = "postgresql";
|
||||
database = cfg.databaseName;
|
||||
host = cfg.databaseHost;
|
||||
password = cfg.databasePassword;
|
||||
username = cfg.databaseUsername;
|
||||
encoding = "utf8";
|
||||
pool = cfg.databasePool;
|
||||
|
@ -66,13 +64,6 @@ let
|
|||
|
||||
redisConfig.production.url = "redis://localhost:6379/";
|
||||
|
||||
secretsConfig.production = {
|
||||
secret_key_base = cfg.secrets.secret;
|
||||
otp_key_base = cfg.secrets.otp;
|
||||
db_key_base = cfg.secrets.db;
|
||||
openid_connect_signing_key = cfg.secrets.jws;
|
||||
};
|
||||
|
||||
gitlabConfig = {
|
||||
# These are the default settings from config/gitlab.example.yml
|
||||
production = flip recursiveUpdate cfg.extraConfig {
|
||||
|
@ -180,10 +171,11 @@ let
|
|||
address: "${cfg.smtp.address}",
|
||||
port: ${toString cfg.smtp.port},
|
||||
${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
|
||||
${optionalString (cfg.smtp.password != null) ''password: "${cfg.smtp.password}",''}
|
||||
${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
|
||||
domain: "${cfg.smtp.domain}",
|
||||
${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
|
||||
enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto},
|
||||
ca_file: "/etc/ssl/certs/ca-certificates.crt",
|
||||
openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
|
||||
}
|
||||
end
|
||||
|
@ -244,13 +236,33 @@ in {
|
|||
|
||||
databaseHost = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Gitlab database hostname.";
|
||||
default = "";
|
||||
description = ''
|
||||
Gitlab database hostname. An empty string means <quote>use
|
||||
local unix socket connection</quote>.
|
||||
'';
|
||||
};
|
||||
|
||||
databasePassword = mkOption {
|
||||
type = types.str;
|
||||
description = "Gitlab database user password.";
|
||||
databasePasswordFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing the Gitlab database user password.
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
databaseCreateLocally = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether a database should be automatically created on the
|
||||
local host. Set this to <literal>false</literal> if you plan
|
||||
on provisioning a local database yourself or use an external
|
||||
one.
|
||||
'';
|
||||
};
|
||||
|
||||
databaseName = mkOption {
|
||||
|
@ -338,10 +350,15 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
initialRootPassword = mkOption {
|
||||
type = types.str;
|
||||
initialRootPasswordFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Initial password of the root account if this is a new install.
|
||||
File containing the initial password of the root account if
|
||||
this is a new install.
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -365,15 +382,20 @@ in {
|
|||
};
|
||||
|
||||
username = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "Username of the SMTP server for Gitlab.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Password of the SMTP server for Gitlab.";
|
||||
description = ''
|
||||
File containing the password of the SMTP server for Gitlab.
|
||||
|
||||
This should be a string, not a nix path, since nix paths
|
||||
are copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
|
@ -383,7 +405,7 @@ in {
|
|||
};
|
||||
|
||||
authentication = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
|
||||
};
|
||||
|
@ -401,68 +423,125 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
secrets.secret = mkOption {
|
||||
type = types.str;
|
||||
secrets.secretFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
The secret is used to encrypt variables in the DB. If
|
||||
you change or lose this key you will be unable to access variables
|
||||
stored in database.
|
||||
A file containing the secret used to encrypt variables in
|
||||
the DB. If you change or lose this key you will be unable to
|
||||
access variables stored in database.
|
||||
|
||||
Make sure the secret is at least 30 characters and all random,
|
||||
no regular words or you'll be exposed to dictionary attacks.
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
secrets.db = mkOption {
|
||||
type = types.str;
|
||||
secrets.dbFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
The secret is used to encrypt variables in the DB. If
|
||||
you change or lose this key you will be unable to access variables
|
||||
stored in database.
|
||||
A file containing the secret used to encrypt variables in
|
||||
the DB. If you change or lose this key you will be unable to
|
||||
access variables stored in database.
|
||||
|
||||
Make sure the secret is at least 30 characters and all random,
|
||||
no regular words or you'll be exposed to dictionary attacks.
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
secrets.otp = mkOption {
|
||||
type = types.str;
|
||||
secrets.otpFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
The secret is used to encrypt secrets for OTP tokens. If
|
||||
you change or lose this key, users which have 2FA enabled for login
|
||||
won't be able to login anymore.
|
||||
A file containing the secret used to encrypt secrets for OTP
|
||||
tokens. If you change or lose this key, users which have 2FA
|
||||
enabled for login won't be able to login anymore.
|
||||
|
||||
Make sure the secret is at least 30 characters and all random,
|
||||
no regular words or you'll be exposed to dictionary attacks.
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
secrets.jws = mkOption {
|
||||
type = types.str;
|
||||
secrets.jwsFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
The secret is used to encrypt session keys. If you change or lose
|
||||
this key, users will be disconnected.
|
||||
A file containing the secret used to encrypt session
|
||||
keys. If you change or lose this key, users will be
|
||||
disconnected.
|
||||
|
||||
Make sure the secret is an RSA private key in PEM format. You can
|
||||
generate one with
|
||||
|
||||
openssl genrsa 2048
|
||||
|
||||
This should be a string, not a nix path, since nix paths are
|
||||
copied into the world-readable nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = {
|
||||
gitlab = {
|
||||
default_projects_features = {
|
||||
builds = false;
|
||||
example = literalExample ''
|
||||
{
|
||||
gitlab = {
|
||||
default_projects_features = {
|
||||
builds = false;
|
||||
};
|
||||
};
|
||||
omniauth = {
|
||||
enabled = true;
|
||||
auto_sign_in_with_provider = "openid_connect";
|
||||
allow_single_sign_on = ["openid_connect"];
|
||||
block_auto_created_users = false;
|
||||
providers = [
|
||||
{
|
||||
name = "openid_connect";
|
||||
label = "OpenID Connect";
|
||||
args = {
|
||||
name = "openid_connect";
|
||||
scope = ["openid" "profile"];
|
||||
response_type = "code";
|
||||
issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
|
||||
discovery = true;
|
||||
client_auth_method = "query";
|
||||
uid_field = "preferred_username";
|
||||
client_options = {
|
||||
identifier = "gitlab";
|
||||
secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
|
||||
redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Extra options to be merged into config/gitlab.yml as nix
|
||||
attribute set.
|
||||
Extra options to be added under
|
||||
<literal>production</literal> in
|
||||
<filename>config/gitlab.yml</filename>, as a nix attribute
|
||||
set.
|
||||
|
||||
Options containing secret data should be set to an attribute
|
||||
set containing the attribute <literal>_secret</literal> - a
|
||||
string pointing to a file containing the value the option
|
||||
should be set to. See the example to get a better picture of
|
||||
this: in the resulting
|
||||
<filename>config/gitlab.yml</filename> file, the
|
||||
<literal>production.omniauth.providers[0].args.client_options.secret</literal>
|
||||
key will be set to the contents of the
|
||||
<filename>/var/keys/gitlab_oidc_secret</filename> file.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
@ -470,12 +549,66 @@ in {
|
|||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.databaseCreateLocally -> (cfg.user == cfg.databaseUsername);
|
||||
message = "For local automatic database provisioning services.gitlab.user and services.gitlab.databaseUsername should be identical.";
|
||||
}
|
||||
{
|
||||
assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
|
||||
message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
|
||||
}
|
||||
{
|
||||
assertion = cfg.initialRootPasswordFile != null;
|
||||
message = "services.gitlab.initialRootPasswordFile must be set!";
|
||||
}
|
||||
{
|
||||
assertion = cfg.secrets.secretFile != null;
|
||||
message = "services.gitlab.secrets.secretFile must be set!";
|
||||
}
|
||||
{
|
||||
assertion = cfg.secrets.dbFile != null;
|
||||
message = "services.gitlab.secrets.dbFile must be set!";
|
||||
}
|
||||
{
|
||||
assertion = cfg.secrets.otpFile != null;
|
||||
message = "services.gitlab.secrets.otpFile must be set!";
|
||||
}
|
||||
{
|
||||
assertion = cfg.secrets.jwsFile != null;
|
||||
message = "services.gitlab.secrets.jwsFile must be set!";
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
|
||||
|
||||
# Redis is required for the sidekiq queue runner.
|
||||
services.redis.enable = mkDefault true;
|
||||
|
||||
# We use postgres as the main data store.
|
||||
services.postgresql.enable = mkDefault true;
|
||||
services.postgresql = optionalAttrs cfg.databaseCreateLocally {
|
||||
enable = true;
|
||||
ensureUsers = singleton { name = cfg.databaseUsername; };
|
||||
};
|
||||
# The postgresql module doesn't currently support concepts like
|
||||
# objects owners and extensions; for now we tack on what's needed
|
||||
# here.
|
||||
systemd.services.postgresql.postStart = mkAfter (optionalString cfg.databaseCreateLocally ''
|
||||
$PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
|
||||
current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
|
||||
if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
|
||||
$PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
|
||||
if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
|
||||
echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
|
||||
exit 1
|
||||
fi
|
||||
touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
|
||||
$PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
|
||||
rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
|
||||
fi
|
||||
$PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
|
||||
'');
|
||||
|
||||
# Use postfix to send out mails.
|
||||
services.postfix.enable = mkDefault true;
|
||||
|
||||
|
@ -527,14 +660,9 @@ in {
|
|||
|
||||
"L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
|
||||
|
||||
"L+ ${cfg.statePath}/config/gitlab.yml - - - - ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)}"
|
||||
"L+ ${cfg.statePath}/config/database.yml - - - - ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)}"
|
||||
"L+ ${cfg.statePath}/config/secrets.yml - - - - ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)}"
|
||||
"L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
|
||||
|
||||
"L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
|
||||
] ++ optional cfg.smtp.enable
|
||||
"L+ ${cfg.statePath}/config/initializers/smtp_settings.rb - - - - ${smtpSettings}" ;
|
||||
];
|
||||
|
||||
systemd.services.gitlab-sidekiq = {
|
||||
after = [ "network.target" "redis.service" "gitlab.service" ];
|
||||
|
@ -626,46 +754,75 @@ in {
|
|||
gnupg
|
||||
];
|
||||
preStart = ''
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} rm -rf ${cfg.statePath}/db/*
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
|
||||
cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
|
||||
rm -rf ${cfg.statePath}/db/*
|
||||
cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
|
||||
cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
|
||||
|
||||
${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
|
||||
${cfg.packages.gitlab-shell}/bin/install
|
||||
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} ${cfg.packages.gitlab-shell}/bin/install
|
||||
${optionalString cfg.smtp.enable ''
|
||||
install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
|
||||
${optionalString (cfg.smtp.passwordFile != null) ''
|
||||
smtp_password=$(<'${cfg.smtp.passwordFile}')
|
||||
${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
|
||||
''}
|
||||
''}
|
||||
|
||||
if ! test -e "${cfg.statePath}/db-created"; then
|
||||
if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
|
||||
${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "CREATE ROLE ${cfg.databaseUsername} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'"
|
||||
${pkgs.sudo}/bin/sudo -u ${pgSuperUser} ${config.services.postgresql.package}/bin/createdb --owner ${cfg.databaseUsername} ${cfg.databaseName}
|
||||
(
|
||||
umask u=rwx,g=,o=
|
||||
|
||||
# enable required pg_trgm extension for gitlab
|
||||
${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql ${cfg.databaseName} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
|
||||
${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
|
||||
|
||||
${if cfg.databasePasswordFile != null then ''
|
||||
export db_password="$(<'${cfg.databasePasswordFile}')"
|
||||
|
||||
if [[ -z "$db_password" ]]; then
|
||||
>&2 echo "Database password was an empty string!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
|
||||
'.production.password = $ENV.db_password' \
|
||||
>'${cfg.statePath}/config/database.yml'
|
||||
''
|
||||
else ''
|
||||
${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
|
||||
>'${cfg.statePath}/config/database.yml'
|
||||
''
|
||||
}
|
||||
|
||||
${utils.genJqSecretsReplacementSnippet
|
||||
gitlabConfig
|
||||
"${cfg.statePath}/config/gitlab.yml"
|
||||
}
|
||||
|
||||
if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then
|
||||
rm '${cfg.statePath}/config/secrets.yml'
|
||||
fi
|
||||
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load
|
||||
export secret="$(<'${cfg.secrets.secretFile}')"
|
||||
export db="$(<'${cfg.secrets.dbFile}')"
|
||||
export otp="$(<'${cfg.secrets.otpFile}')"
|
||||
export jws="$(<'${cfg.secrets.jwsFile}')"
|
||||
${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret,
|
||||
otp_key_base: $ENV.db,
|
||||
db_key_base: $ENV.otp,
|
||||
openid_connect_signing_key: $ENV.jws}}' \
|
||||
> '${cfg.statePath}/config/secrets.yml'
|
||||
)
|
||||
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-created"
|
||||
fi
|
||||
|
||||
# Always do the db migrations just to be sure the database is up-to-date
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:migrate
|
||||
|
||||
if ! test -e "${cfg.statePath}/db-seeded"; then
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \
|
||||
GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-seeded"
|
||||
fi
|
||||
initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
|
||||
${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
|
||||
GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
|
||||
|
||||
# We remove potentially broken links to old gitlab-shell versions
|
||||
rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
|
||||
|
||||
${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${pkgs.git}/bin/git config --global core.autocrlf "input"
|
||||
${pkgs.git}/bin/git config --global core.autocrlf "input"
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
PermissionsStartOnly = true; # preStart must be run as root
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
<programlisting>
|
||||
services.gitlab = {
|
||||
<link linkend="opt-services.gitlab.enable">enable</link> = true;
|
||||
<link linkend="opt-services.gitlab.databasePassword">databasePassword</link> = "eXaMpl3";
|
||||
<link linkend="opt-services.gitlab.initialRootPassword">initialRootPassword</link> = "UseNixOS!";
|
||||
<link linkend="opt-services.gitlab.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
|
||||
<link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
|
||||
<link linkend="opt-services.gitlab.https">https</link> = true;
|
||||
<link linkend="opt-services.gitlab.host">host</link> = "git.example.com";
|
||||
<link linkend="opt-services.gitlab.port">port</link> = 443;
|
||||
|
@ -67,38 +67,10 @@ services.gitlab = {
|
|||
<link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
|
||||
};
|
||||
secrets = {
|
||||
<link linkend="opt-services.gitlab.secrets.db">db</link> = "uPgq1gtwwHiatiuE0YHqbGa5lEIXH7fMsvuTNgdzJi8P0Dg12gibTzBQbq5LT7PNzcc3BP9P1snHVnduqtGF43PgrQtU7XL93ts6gqe9CBNhjtaqUwutQUDkygP5NrV6";
|
||||
<link linkend="opt-services.gitlab.secrets.secret">secret</link> = "devzJ0Tz0POiDBlrpWmcsjjrLaltyiAdS8TtgT9YNBOoUcDsfppiY3IXZjMVtKgXrFImIennFGOpPN8IkP8ATXpRgDD5rxVnKuTTwYQaci2NtaV1XxOQGjdIE50VGsR3";
|
||||
<link linkend="opt-services.gitlab.secrets.otp">otp</link> = "e1GATJVuS2sUh7jxiPzZPre4qtzGGaS22FR50Xs1TerRVdgI3CBVUi5XYtQ38W4xFeS4mDqi5cQjExE838iViSzCdcG19XSL6qNsfokQP9JugwiftmhmCadtsnHErBMI";
|
||||
<link linkend="opt-services.gitlab.secrets.jws">jws</link> = ''
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEArrtx4oHKwXoqUbMNqnHgAklnnuDon3XG5LJB35yPsXKv/8GK
|
||||
ke92wkI+s1Xkvsp8tg9BIY/7c6YK4SR07EWL+dB5qwctsWR2Q8z+/BKmTx9D99pm
|
||||
hnsjuNIXTF7BXrx3RX6BxZpH5Vzzh9nCwWKT/JCFqtwH7afNGGL7aMf+hdaiUg/Q
|
||||
SD05yRObioiO4iXDolsJOhrnbZvlzVHl1ZYxFJv0H6/Snc0BBA9Fl/3uj6ANpbjP
|
||||
eXF1SnJCqT87bj46r5NdVauzaRxAsIfqHroHK4UZ98X5LjGQFGvSqTvyjPBS4I1i
|
||||
s7VJU28ObuutHxIxSlH0ibn4HZqWmKWlTS652wIDAQABAoIBAGtPcUTTw2sJlR3x
|
||||
4k2wfAvLexkHNbZhBdKEa5JiO5mWPuLKwUiZEY2CU7Gd6csG3oqNWcm7/IjtC7dz
|
||||
xV8p4yp8T4yq7vQIJ93B80NqTLtBD2QTvG2RCMJEPMzJUObWxkVmyVpLQyZo7KOd
|
||||
KE/OM+aj94OUeEYLjRkSCScz1Gvq/qFG/nAy7KPCmN9JDHuhX26WHo2Rr1OnPNT/
|
||||
7diph0bB9F3b8gjjNTqXDrpdAqVOgR/PsjEBz6DMY+bdyMIn87q2yfmMexxRofN6
|
||||
LulpzSaa6Yup8N8H6PzVO6KAkQuf1aQRj0sMwGk1IZEnj6I0KbuHIZkw21Nc6sf2
|
||||
ESFySDECgYEA1PnCNn5tmLnwe62Ttmrzl20zIS3Me1gUVJ1NTfr6+ai0I9iMYU21
|
||||
5czuAjJPm9JKQF2vY8UAaCj2ZoObtHa/anb3xsCd8NXoM3iJq5JDoXI1ldz3Y+ad
|
||||
U/bZUg1DLRvAniTuXmw9iOTwTwPxlDIGq5k+wG2Xmi1lk7zH8ezr9BMCgYEA0gfk
|
||||
EhgcmPH8Z5cU3YYwOdt6HSJOM0OyN4k/5gnkv+HYVoJTj02gkrJmLr+mi1ugKj46
|
||||
7huYO9TVnrKP21tmbaSv1dp5hS3letVRIxSloEtVGXmmdvJvBRzDWos+G+KcvADi
|
||||
fFCz6w8v9NmO40CB7y/3SxTmSiSxDQeoi9LhDBkCgYEAsPgMWm25sfOnkY2NNUIv
|
||||
wT8bAlHlHQT2d8zx5H9NttBpR3P0ShJhuF8N0sNthSQ7ULrIN5YGHYcUH+DyLAWU
|
||||
TuomP3/kfa+xL7vUYb269tdJEYs4AkoppxBySoz8qenqpz422D0G8M6TpIS5Y5Qi
|
||||
GMrQ6uLl21YnlpiCaFOfSQMCgYEAmZxj1kgEQmhZrnn1LL/D7czz1vMMNrpAUhXz
|
||||
wg9iWmSXkU3oR1sDIceQrIhHCo2M6thwyU0tXjUft93pEQocM/zLDaGoVxtmRxxV
|
||||
J08mg8IVD3jFoyFUyWxsBIDqgAKRl38eJsXvkO+ep3mm49Z+Ma3nM+apN3j2dQ0w
|
||||
3HLzXaECgYBFLMEAboVFwi5+MZjGvqtpg2PVTisfuJy2eYnPwHs+AXUgi/xRNFjI
|
||||
YHEa7UBPb5TEPSzWImQpETi2P5ywcUYL1EbN/nqPWmjFnat8wVmJtV4sUpJhubF4
|
||||
Vqm9LxIWc1uQ1q1HDCejRIxIN3aSH+wgRS3Kcj8kCTIoXd1aERb04g==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
'';
|
||||
<link linkend="opt-services.gitlab.secrets.dbFile">dbFile</link> = "/var/keys/gitlab/db";
|
||||
<link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
|
||||
<link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
|
||||
<link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
|
||||
};
|
||||
<link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = {
|
||||
gitlab = {
|
||||
|
@ -113,12 +85,16 @@ services.gitlab = {
|
|||
</para>
|
||||
|
||||
<para>
|
||||
If you're setting up a new Gitlab instance, generate new secrets. You for
|
||||
instance use <literal>tr -dc A-Za-z0-9 < /dev/urandom | head -c
|
||||
128</literal> to generate a new secret. Gitlab encrypts sensitive data
|
||||
stored in the database. If you're restoring an existing Gitlab instance, you
|
||||
must specify the secrets secret from <literal>config/secrets.yml</literal>
|
||||
located in your Gitlab state folder.
|
||||
If you're setting up a new Gitlab instance, generate new
|
||||
secrets. You for instance use <literal>tr -dc A-Za-z0-9 <
|
||||
/dev/urandom | head -c 128 > /var/keys/gitlab/db</literal> to
|
||||
generate a new db secret. Make sure the files can be read by, and
|
||||
only by, the user specified by <link
|
||||
linkend="opt-services.gitlab.user">services.gitlab.user</link>. Gitlab
|
||||
encrypts sensitive data stored in the database. If you're restoring
|
||||
an existing Gitlab instance, you must specify the secrets secret
|
||||
from <literal>config/secrets.yml</literal> located in your Gitlab
|
||||
state folder.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
|
|
@ -29,44 +29,14 @@ import ./make-test.nix ({ pkgs, lib, ...} : with lib; {
|
|||
|
||||
services.gitlab = {
|
||||
enable = true;
|
||||
databasePassword = "dbPassword";
|
||||
inherit initialRootPassword;
|
||||
databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
|
||||
initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
|
||||
smtp.enable = true;
|
||||
secrets = {
|
||||
secret = "secret";
|
||||
otp = "otpsecret";
|
||||
db = "dbsecret";
|
||||
|
||||
# nix-shell -p openssl --run "openssl genrsa 2048"
|
||||
jws = ''
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA13/qEio76OWUtWO0WIz9lWnsTWOU8Esv4sQHDq9PCEFsLt21
|
||||
PAXrlWhLjjWcxGfsrDwnh7YErGHYL62BMSxMdFJolaknlQK/O/V8UETDe45VoHM+
|
||||
Znk270RfUcfYFgiihnXUZXVmL0om9TsQSk646wCcjCY9LxtxUyKNhvT7KjgYw2aX
|
||||
z34aw7M+Js3T2p1TjZPSC82GtmtKkJEKFMi5EjprLTDE7EdcUzr9Xuw+kQ+gRm9k
|
||||
7FE+JQqSoprwE3Q0v2OAn3UhLMgg0gNFRnsc5l6IAshDzV+H22RPqKKlJjVjjfPY
|
||||
0TQSvYLVApigHbDPH0BoCXfjFfQazbbP3OUHrwIDAQABAoIBAQCMU+tkcMQaYIV5
|
||||
qLdjgkwO467QpivyXcOM8wF1eosIYTHFQvIlZ+WEoSmyLQ8shlADyBgls01Pw1c3
|
||||
lNAv6RzQEmmwKzpvOh61OKH+0whIiOMRXHoh2IUBQZCgfHYlwvGyhUAN4WjtGmhM
|
||||
AG4XNTQNM5S9Xpkw97nP3Qwz+YskbbkrfqtCEVy9ro+4nhbjqPsuO3adbnkva4zR
|
||||
cyurRhrHgHU6LPjn5NHnHH4qw2faY2oAsL8pmpkTbO5IqWDvOcbjNfjVPgVoq26O
|
||||
bbaa1qs4nmc80qQgMjRPJef535xyf3eLsSlDvpf6O8sPrJzVR1zaqEqixpQCZDac
|
||||
+kRiSBrhAoGBAOwHiq0PuyJh6VzBu7ybqX6+gF/wA4Jkwzx6mbfaBgurvU1aospp
|
||||
kisIonAkxSbxllZMnjbkShZEdATYKeT9o5NEhnU4YnHfc5bJZbiWOZAzYGLcY7g8
|
||||
vDQ31pBItyY4pFgPbSpNlbUvUsoPVJ45RasRADDTNCzMzdjFQQXst2V9AoGBAOm7
|
||||
sSpzYfFPLEAhieAkuhtbsX58Boo46djiKVfzGftfp6F9aHTOfzGORU5jrZ16mSbS
|
||||
qkkC6BEFrATX2051dzzXC89fWoJYALrsffE5I3KlKXsCAWSnCP1MMxOfH+Ls61Mr
|
||||
7pK/LKfvJt53mUH4jIdbmmFUDwbg18oBEH+x9PmbAoGAS/+JqXu9N67rIxDGUE6W
|
||||
3tacI0f2+U9Uhe67/DTZaXyc8YFTlXU0uWKIWy+bw5RaYeM9tlL/f/f+m2i25KK+
|
||||
vrZ7zNag7CWU5GJovGyykDnauTpZaYM03mN0VPT08/uc/zXIYqyknbhlIeaZynCK
|
||||
fDB3LUF0NVCknz20WCIGU0kCgYEAkxY0ZXx61Dp4pFr2wwEZxQGs7uXpz64FKyEX
|
||||
12r6nMATY4Lh6y/Px0W6w5vis8lk+5Ny6cNUevHQ0LNuJS+yu6ywl+1vrbrnqroM
|
||||
f3LvpcPeGLSoX8jl1VDQi7aFgG6LoKly1xJLbdsH4NPutB9PgBbbTghx9GgmI88L
|
||||
rPA2M6UCgYBOmkYJocNgxg6B1/n4Tb9fN1Q/XuJrFDE6NxVUoke+IIyMPRH7FC3m
|
||||
VMYzu+b7zTVJjaBb1cmJemxl/xajziWDofJYPefhdbOVU7HXtmJFY0IG3pVxU1zW
|
||||
3bmDj5QAtCUDpuuNa6GEIT0YR4+D/V7o3DmlZ0tVIwKJmVJoQ2f5dw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
'';
|
||||
secretFile = pkgs.writeText "secret" "Aig5zaic";
|
||||
otpFile = pkgs.writeText "otpsecret" "Riew9mue";
|
||||
dbFile = pkgs.writeText "dbsecret" "we2quaeZ";
|
||||
jwsFile = pkgs.runCommand "oidcKeyBase" {} "${pkgs.openssl}/bin/openssl genrsa 2048 > $out";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue