From 60ae321e75b6b3153ef71abd4879bf87e8db4efd Mon Sep 17 00:00:00 2001 From: Thierry Bordaz Date: Feb 26 2020 09:58:19 +0000 Subject: Ticket 50889 - Extract pem files into a private namespace Bug Description: since 1.3.5, certificates and keys are, by default, extracted under nsslapd-certdir directory. They are exracted in pem files. Some pem files (i.e. Serv-Cert-Key.pem) contain sensitive. The ticket is to extract them into a private namespace specific to the DS process. Fix Description: If the process is started with systemd, it uses the PrivateTmp=on directive to create a private namespace. Then if such private namespace exists DS extracts the certificates/keys under it. Else it extracts the PEM files under usual nsslapd-certdir directory https://pagure.io/389-ds-base/issue/50889 Reviewed by: William Brown Platforms tested: F30 Doc impact: yes --- diff --git a/ldap/admin/src/scripts/dscreate.map.in b/ldap/admin/src/scripts/dscreate.map.in index fcf6e7a..92cefd0 100644 --- a/ldap/admin/src/scripts/dscreate.map.in +++ b/ldap/admin/src/scripts/dscreate.map.in @@ -37,3 +37,4 @@ log_dir = log_dir config_dir = config_dir db_dir = db_dir run_dir = run_dir +instance_name = ServerIdentifier diff --git a/ldap/servers/slapd/back-ldbm/misc.c b/ldap/servers/slapd/back-ldbm/misc.c index 124d5f6..4b53e32 100644 --- a/ldap/servers/slapd/back-ldbm/misc.c +++ b/ldap/servers/slapd/back-ldbm/misc.c @@ -318,71 +318,6 @@ ldbm_delete_dirs(char *path) return rval; } -char -get_sep(char *path) -{ - if (NULL == path) - return '/'; /* default */ - if (NULL != strchr(path, '/')) - return '/'; - if (NULL != strchr(path, '\\')) - return '\\'; - return '/'; /* default */ -} - -/* mkdir -p */ -int -mkdir_p(char *dir, unsigned int mode) -{ - PRFileInfo64 info; - int rval; - char sep = get_sep(dir); - - rval = PR_GetFileInfo64(dir, &info); - if (PR_SUCCESS == rval) { - if (PR_FILE_DIRECTORY != info.type) /* not a directory */ - { - PR_Delete(dir); - if (PR_SUCCESS != PR_MkDir(dir, mode)) { - slapi_log_err(SLAPI_LOG_ERR, "mkdir_p", "%s: error %d (%s)\n", - dir, PR_GetError(), slapd_pr_strerror(PR_GetError())); - return -1; - } - } - return 0; - } else { - /* does not exist */ - char *p, *e; - char c[2] = {0, 0}; - int len = strlen(dir); - rval = 0; - - e = dir + len - 1; - if (*e == sep) { - c[1] = *e; - *e = '\0'; - } - - c[0] = '/'; - p = strrchr(dir, sep); - if (NULL != p) { - *p = '\0'; - rval = mkdir_p(dir, mode); - *p = c[0]; - } - if (c[1]) - *e = c[1]; - if (0 != rval) - return rval; - if (PR_SUCCESS != PR_MkDir(dir, mode)) { - slapi_log_err(SLAPI_LOG_ERR, "mkdir_p", "%s: error %d (%s)\n", - dir, PR_GetError(), slapd_pr_strerror(PR_GetError())); - return -1; - } - return 0; - } -} - /* This routine checks to see if there is a callback registered for retrieving * RUV updates to add to the datastore transaction. If so, it allocates a * modify_context for consumption by the caller. */ diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c index 5971376..336ca39 100644 --- a/ldap/servers/slapd/ldaputil.c +++ b/ldap/servers/slapd/ldaputil.c @@ -508,12 +508,22 @@ slapi_ldif_parse_line( static int setup_ol_tls_conn(LDAP *ld, int clientauth) { - char *certdir = config_get_certdir(); + char *certdir; int optval = 0; int ssl_strength = 0; int rc = 0; const char *cacert = NULL; + /* certdir is used to setup outgoing secure connection (openldap) + * It refers to the place where PEM files have been extracted + * + * If a private tmp namespace exists + * it is the place where PEM files have been extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } + if (config_get_ssl_check_hostname()) { ssl_strength = LDAP_OPT_X_TLS_HARD; } else { diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h index 0219912..17ef53e 100644 --- a/ldap/servers/slapd/slapi-private.h +++ b/ldap/servers/slapd/slapi-private.h @@ -1223,6 +1223,8 @@ const char *escape_string_for_filename(const char *str, char buf[BUFSIZ]); void strcpy_unescape_value(char *d, const char *s); void get_internal_conn_op (uint64_t *connid, int32_t *op_id, int32_t *op_internal_id, int32_t *op_nested_count); char *slapi_berval_get_string_copy(const struct berval *bval); +char get_sep(char *path); +int mkdir_p(char *dir, unsigned int mode); /* lenstr stuff */ diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c index 9296cd4..6ea4ae0 100644 --- a/ldap/servers/slapd/ssl.c +++ b/ldap/servers/slapd/ssl.c @@ -913,6 +913,85 @@ slapi_getSSLVersion_str(PRUint16 vnum, char *buf, size_t bufsize) #define SSLVGreater(x, y) (((x) > (y)) ? (x) : (y)) +/* This routine returns the absolute path where to extract the pem files + * (certificate/key) in case nsslapd-private-certdir is private namespace. + * If absolute patch does not exist, it creates it with right 0777 + * + * If the configuration parameter is not defined it returns NULL + * + * If the configuration parameter (nsslapd-private-certdir) is defined and + * valid (under /tmp namespace) it returns it. + * + * If /tmp is not a private namespace or configaration parameter is invalid + * it returns NULL + * + * If returned value is not NULL, caller must free it (slapi_ch_free_string) + */ +char * +check_private_certdir() +{ + FILE *f; + char sline[1024]; + const char *private_namespace_root = "/systemd-private"; + const char *private_mountpoint = "/tmp"; /* Using systemd PrivateTmp=Yes, "/tmp" is expected to be private */ + int mountid, parentid, major, minor; + char root[256] = {0}; /* path which forms the root of the mount */ + char mountpoint[256] = {0}; /* path of the mountpoint relative to process root directory */ + char rest[256] = {0}; + PRBool tmp_private = PR_FALSE; + char *path_certdir; + char *certdir; + char *bname = NULL; + + /* Check if /tmp is a private namespace */ + f = fopen("/proc/self/mountinfo", "r"); + if (f == NULL) { + return NULL; + } + while (fgets(sline, sizeof(sline), f)) { + sscanf(sline,"%d %d %d:%d %s %s %s\n", + &mountid, &parentid, &major, &minor, &root, &mountpoint, &rest); + if ((strncmp(mountpoint, private_mountpoint, strlen(private_mountpoint)) == 0) && /* mountpoint=/tmp */ + strstr(root, private_namespace_root)) { /* root=...systemd-private... */ + tmp_private = PR_TRUE; + break; + } + } + fclose(f); + + if (!tmp_private) { + /* tmp is not a private name space */ + slapi_log_err(SLAPI_LOG_WARNING, "Security Initialization", + "%s is not a private namespace. pem files not exported there\n", + private_mountpoint); + return NULL; + } + + /* Create the subdirectory under the private tmp namespace + * e.g.: /tmp/slapd-standalone1 + */ + certdir = config_get_certdir(); + if (certdir == NULL) { + /* config does not define a certdir path, extract pem + * under private_mountpoint + */ + bname = ""; + } else { + bname = basename(certdir); + } + path_certdir = slapi_ch_smprintf("%s/%s", private_mountpoint, bname); + slapi_ch_free_string(&certdir); + + if (mkdir_p(path_certdir, 0777)) { + slapi_log_err(SLAPI_LOG_WARNING, "Security Initialization", + "check_private_certdir - Fail to create %s\n", + path_certdir); + slapi_ch_free_string(&path_certdir); + return NULL; + } + return path_certdir; +} + /* * slapd_nss_init() is always called from main(), even if we do not * plan to listen on a secure port. If config_available is 0, the @@ -945,7 +1024,9 @@ slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attr emin, emax); /* set in slapd_bootstrap_config, - thus certdir is available even if config_available is false */ + thus certdir is available even if config_available is false + certdir is the path where the NSS database is located + */ certdir = config_get_certdir(); /* make sure path does not end in the path separator character */ @@ -2105,9 +2186,15 @@ slapd_SSL_client_auth(LDAP *ld) errorCode, slapd_pr_strerror(errorCode)); } else { if (slapi_client_uses_non_nss(ld) && config_get_extract_pem()) { - char *certdir = config_get_certdir(); + char *certdir; char *keyfile = NULL; char *certfile = NULL; + /* If a private tmp namespace exists + * it is the place where PEM files will be extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } if (KeyExtractFile) { if ('/' == *KeyExtractFile) { keyfile = KeyExtractFile; @@ -2402,6 +2489,13 @@ listCerts(CERTCertDBHandle *handle, CERTCertificate *cert, PK11SlotInfo *slot __ return rv; } +/* This function is called to get the path of PEM file where + * to extract key/cert (called by slapd_extract_key/cert). + * If filename is absolute path, it is the responsibility of the admin + * to set it according CONFIG_CERTDIR_ATTRIBUTE + * Else if it exists a private tmp namespace it locates the PEM file under it + * Else it locates the PEM file under CONFIG_CERTDIR_ATTRIBUTE + */ static char * gen_pem_path(char *filename) { @@ -2410,11 +2504,17 @@ gen_pem_path(char *filename) char *dname = NULL; char *bname = NULL; char *certdir = NULL; + char *default_certdir = config_get_certdir(); if (!filename) { goto bail; } - certdir = config_get_certdir(); + /* If a private tmp namespace exists + * it is the place where PEM file will be extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } pem = PL_strstr(filename, PEMEXT); if (pem) { *pem = '\0'; @@ -2424,6 +2524,11 @@ gen_pem_path(char *filename) if (!PL_strcmp(dname, ".")) { /* just a file name */ pempath = slapi_ch_smprintf("%s/%s%s", certdir, bname, PEMEXT); + } else if (!PL_strcmp(dname, default_certdir)) { + /* This is absolute path to certdir (e.g. /etc/dirsrv/slapd-standalone1) + * PEM file will be in private namespace or certdir + */ + pempath = slapi_ch_smprintf("%s/%s%s", certdir, bname, PEMEXT); } else if (*dname == '/') { /* full path */ pempath = slapi_ch_smprintf("%s/%s%s", dname, bname, PEMEXT); @@ -2432,6 +2537,7 @@ gen_pem_path(char *filename) pempath = slapi_ch_smprintf("%s/%s/%s%s", certdir, dname, bname, PEMEXT); } bail: + slapi_ch_free_string(&default_certdir); slapi_ch_free_string(&certdir); return pempath; } @@ -2462,13 +2568,13 @@ slapd_extract_cert(Slapi_Entry *entry, int isCA) CertExtractFile = slapi_entry_attr_get_charptr(entry, "ServerCertExtractFile"); personality = slapi_entry_attr_get_charptr(entry, "nsSSLPersonalitySSL"); } + certfile = gen_pem_path(CertExtractFile); if (isCA) { slapi_ch_free_string(&CACertPemFile); CACertPemFile = certfile; } - certdir = config_get_certdir(); certHandle = CERT_GetDefaultCertDB(); for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); node = CERT_LIST_NEXT(node)) { @@ -2483,8 +2589,15 @@ slapd_extract_cert(Slapi_Entry *entry, int isCA) slapi_log_err(SLAPI_LOG_INFO, "slapd_extract_cert", "CA CERT NAME: %s\n", cert->nickname); if (!certfile) { char buf[BUFSIZ]; + /* If a private tmp namespace exists + * it is the place where PEM file will be extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } certfile = slapi_ch_smprintf("%s/%s%s", certdir, escape_string_for_filename(cert->nickname, buf), PEMEXT); + slapi_ch_free_string(&certdir); entrySetValue(slapi_entry_get_sdn(entry), "CACertExtractFile", certfile); slapi_set_cacertfile(certfile); } @@ -2510,8 +2623,15 @@ slapd_extract_cert(Slapi_Entry *entry, int isCA) slapi_log_err(SLAPI_LOG_INFO, "slapd_extract_cert", "SERVER CERT NAME: %s\n", cert->nickname); if (!certfile) { char buf[BUFSIZ]; + /* If a private tmp namespace exists + * it is the place where PEM file will be extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } certfile = slapi_ch_smprintf("%s/%s%s", certdir, escape_string_for_filename(cert->nickname, buf), PEMEXT); + slapi_ch_free_string(&certdir); } if (!outFile) { outFile = PR_Open(certfile, PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE, 00660); @@ -2777,10 +2897,15 @@ slapd_extract_key(Slapi_Entry *entry, char *token __attribute__((unused)), PK11S "nsSSLPersonalitySSL value not found.\n"); goto bail; } - certdir = config_get_certdir(); keyfile = gen_pem_path(KeyExtractFile); if (!keyfile) { char buf[BUFSIZ]; + /* If a private tmp namespace exists + * it is the place where PEM file will be extracted + */ + if ((certdir = check_private_certdir()) == NULL) { + certdir = config_get_certdir(); + } keyfile = slapi_ch_smprintf("%s/%s-Key%s", certdir, escape_string_for_filename(personality, buf), PEMEXT); } diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c index e1219c5..6ee1aac 100644 --- a/ldap/servers/slapd/util.c +++ b/ldap/servers/slapd/util.c @@ -1571,3 +1571,68 @@ slapi_fetch_attr(Slapi_Entry *e, const char *attrname, char *default_val) slapi_attr_first_value(attr, &val); return slapi_value_get_string(val); } + +char +get_sep(char *path) +{ + if (NULL == path) + return '/'; /* default */ + if (NULL != strchr(path, '/')) + return '/'; + if (NULL != strchr(path, '\\')) + return '\\'; + return '/'; /* default */ +} + +/* mkdir -p */ +int +mkdir_p(char *dir, unsigned int mode) +{ + PRFileInfo64 info; + int rval; + char sep = get_sep(dir); + + rval = PR_GetFileInfo64(dir, &info); + if (PR_SUCCESS == rval) { + if (PR_FILE_DIRECTORY != info.type) /* not a directory */ + { + PR_Delete(dir); + if (PR_SUCCESS != PR_MkDir(dir, mode)) { + slapi_log_err(SLAPI_LOG_ERR, "mkdir_p", "%s: error %d (%s)\n", + dir, PR_GetError(), slapd_pr_strerror(PR_GetError())); + return -1; + } + } + return 0; + } else { + /* does not exist */ + char *p, *e; + char c[2] = {0, 0}; + int len = strlen(dir); + rval = 0; + + e = dir + len - 1; + if (*e == sep) { + c[1] = *e; + *e = '\0'; + } + + c[0] = '/'; + p = strrchr(dir, sep); + if (NULL != p) { + *p = '\0'; + rval = mkdir_p(dir, mode); + *p = c[0]; + } + if (c[1]) + *e = c[1]; + if (0 != rval) + return rval; + if (PR_SUCCESS != PR_MkDir(dir, mode)) { + slapi_log_err(SLAPI_LOG_ERR, "mkdir_p", "%s: error %d (%s)\n", + dir, PR_GetError(), slapd_pr_strerror(PR_GetError())); + return -1; + } + return 0; + } +} \ No newline at end of file diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py index 77fc4be..8c0273f 100644 --- a/src/lib389/lib389/instance/setup.py +++ b/src/lib389/lib389/instance/setup.py @@ -728,6 +728,7 @@ class SetupDs(object): ds_port=slapd['port'], ds_user=slapd['user'], rootdn=slapd['root_dn'], + instance_name=slapd['instance_name'], ds_passwd=self._secure_password, # We set our own password here, so we can connect and mod. # This is because we never know the users input root password as they can validily give # us a *hashed* input. diff --git a/wrappers/systemd.template.service.in b/wrappers/systemd.template.service.in index 3f2f23a..a8c21a9 100644 --- a/wrappers/systemd.template.service.in +++ b/wrappers/systemd.template.service.in @@ -15,6 +15,7 @@ EnvironmentFile=-@initconfigdir@/@package_name@-%i PIDFile=/run/@package_name@/slapd-%i.pid ExecStartPre=@libexecdir@/ds_systemd_ask_password_acl @instconfigdir@/slapd-%i/dse.ldif ExecStart=@sbindir@/ns-slapd -D @instconfigdir@/slapd-%i -i /run/@package_name@/slapd-%i.pid +PrivateTmp=on [Install] WantedBy=multi-user.target