diff --git a/config/krb5conf.go b/config/krb5conf.go index 8efe92d8..4fcfab6f 100644 --- a/config/krb5conf.go +++ b/config/krb5conf.go @@ -10,6 +10,7 @@ import ( "net" "os" "os/user" + "path/filepath" "regexp" "strconv" "strings" @@ -515,15 +516,60 @@ func (c *Config) ResolveRealm(domainName string) string { return c.LibDefaults.DefaultRealm } -// Load the KRB5 configuration from the specified file path. -func Load(cfgPath string) (*Config, error) { +// Recursively load the contents of the KRB5 config files using include and includedir +func recurLoadFile(cfgPath string) ([]string, error) { fh, err := os.Open(cfgPath) if err != nil { return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error()) } defer fh.Close() + var lines []string + var recurlines []string scanner := bufio.NewScanner(fh) - return NewConfigFromScanner(scanner) + reinclude := regexp.MustCompile(`^include\s*(\S*)$`) + reincludedir := regexp.MustCompile(`^includedir\s*(\S*)$`) + for scanner.Scan() { + result := reincludedir.FindStringSubmatch(scanner.Text()) + if len(result) > 1 { + cfgDir := result[1] + files, err := os.ReadDir(cfgDir) + if err != nil { + return nil, errors.New("configuration directory could not be opened: " + cfgDir + " " + err.Error()) + } + for _, file := range files { + if matched, _ := regexp.MatchString(`^[\w-\.]+$`, file.Name()); matched { + filePath := filepath.Join(cfgDir, file.Name()) + recurlines, err = recurLoadFile(filePath) + if err != nil { + return nil, errors.New("subconfiguration file could not be opened: " + filePath + " " + err.Error()) + } + lines = append(lines, recurlines...) + } + } + continue + } + result = reinclude.FindStringSubmatch(scanner.Text()) + if len(result) > 1 { + subCfgPath := result[1] + recurlines, err = recurLoadFile(subCfgPath) + if err != nil { + return nil, errors.New("subconfiguration file could not be opened: " + subCfgPath + " " + err.Error()) + } + lines = append(lines, recurlines...) + continue + } + lines = append(lines, scanner.Text()) + } + return lines, nil +} + +// Load the KRB5 configuration from the specified file path. +func Load(cfgPath string) (*Config, error) { + lines, err := recurLoadFile(cfgPath) + if err != nil { + return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error()) + } + return NewConfigFromString(strings.Join(lines, "\n")) } // NewConfigFromString creates a new Config struct from a string. @@ -596,12 +642,12 @@ func NewConfigFromScanner(scanner *bufio.Scanner) (*Config, error) { } e = err } - c.Realms = realms + c.Realms = append(c.Realms, realms...) case "domain_realm": err := c.DomainRealm.parseLines(lines[start:end]) if err != nil { if _, ok := err.(UnsupportedDirective); !ok { - return nil, fmt.Errorf("error processing domaain_realm section: %v", err) + return nil, fmt.Errorf("error processing domain_realm section: %v", err) } e = err } diff --git a/config/krb5conf_test.go b/config/krb5conf_test.go index eca779a8..6122f5ea 100644 --- a/config/krb5conf_test.go +++ b/config/krb5conf_test.go @@ -1,8 +1,10 @@ package config import ( + "fmt" "io/ioutil" "os" + "path/filepath" "testing" "time" @@ -10,6 +12,112 @@ import ( ) const ( + krb5ConfIncHead = ` +# To opt out of the system crypto-policies configuration of krb5, remove the +# symlink at /etc/krb5.conf.d/crypto-policies which will not be recreated. +#includedir /etc/krb5.conf.d/ +` + + krb5ConfInc = ` +[logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + +[libdefaults] + dns_lookup_realm = false + ticket_lifetime = 24h + renew_lifetime = 7d + forwardable = true + rdns = false + pkinit_anchors = FILE:/etc/pki/tls/certs/ca-bundle.crt + spake_preauth_groups = edwards25519 +# default_realm = EXAMPLE.COM + default_ccache_name = KEYRING:persistent:%{uid} + +[realms] +# EXAMPLE.COM = { +# kdc = kerberos.example.com +# admin_server = kerberos.example.com +# } + +[domain_realm] +# .example.com = EXAMPLE.COM +# example.com = EXAMPLE.COM +` + krb5ConfIHead = ` +[logging] + default = FILE:/var/log/kerberos/krb5libs.log + kdc = FILE:/var/log/kerberos/krb5kdc.log + admin_server = FILE:/var/log/kerberos/kadmind.log + +` + krb5Include = ` +[libdefaults] + default_realm = TEST.GOKRB5 ; comment to be ignored + dns_lookup_realm = false + + dns_lookup_kdc = false + #dns_lookup_kdc = true + ;dns_lookup_kdc = true +#dns_lookup_kdc = true +;dns_lookup_kdc = true + ticket_lifetime = 10h ;comment to be ignored + forwardable = yes #comment to be ignored + default_keytab_name = FILE:/etc/krb5.keytab + + default_client_keytab_name = FILE:/home/gokrb5/client.keytab + default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 # comment to be ignored + +` + krb5ConfI = ` +[realms] + TEST.GOKRB5 = { + kdc = 10.80.88.88:88 #comment to be ignored + kdc = assume.port.num ;comment to be ignored + kdc = some.other.port:1234 # comment to be ignored + + kdc = 10.80.88.88* + kdc = 10.1.2.3.4:88 + + admin_server = 10.80.88.88:749 ; comment to be ignored + default_domain = test.gokrb5 + } + EXAMPLE.COM = { + kdc = kerberos.example.com + kdc = kerberos-1.example.com + admin_server = kerberos.example.com + auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE.COM)s/.*// + } + lowercase.org = { + kdc = kerberos.lowercase.org + admin_server = kerberos.lowercase.org + } + + +[domain_realm] + .test.gokrb5 = TEST.GOKRB5 #comment to be ignored + + test.gokrb5 = TEST.GOKRB5 ;comment to be ignored + + .example.com = EXAMPLE.COM # comment to be ignored + hostname1.example.com = EXAMPLE.COM ; comment to be ignored + hostname2.example.com = TEST.GOKRB5 + .testlowercase.org = lowercase.org + + +[appdefaults] + pam = { + debug = false + + ticket_lifetime = 36000 + + renew_lifetime = 36000 + forwardable = true + krb4_convert = false + } +` + krb5Conf = ` [logging] default = FILE:/var/log/kerberos/krb5libs.log @@ -308,6 +416,92 @@ const ( ` ) +func TestLoadincludedir(t *testing.T) { + t.Parallel() + cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5inc.conf") + defer os.Remove(cf.Name()) + cdir, _ := ioutil.TempDir("", "example") + defer os.RemoveAll(cdir) + cfgDir := "testkrb5.conf.d" + files, err := os.ReadDir(cfgDir) + if err != nil { + t.Fatalf("Error loading config dir: %v", err) + } + for _, file := range files { + srcPath := filepath.Join(cfgDir, file.Name()) + data, err := ioutil.ReadFile(srcPath) + if err != nil { + t.Fatalf("Error reading config file: %v", err) + } + dstPath := filepath.Join(cdir, file.Name()) + err = ioutil.WriteFile(dstPath, data, 0644) + if err != nil { + t.Fatalf("Error writting config file: %v", err) + } + } + krb5ContentsInc := krb5ConfIncHead + fmt.Sprintf("includedir %s\n", cdir) + krb5ConfInc + cf.WriteString(krb5ContentsInc) + + c, err := Load(cf.Name()) + if err != nil { + t.Fatalf("Error loading config: %v", err) + } + + assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") + assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") + assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") + assert.Equal(t, time.Duration(24)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") + assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") + assert.Equal(t, "/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") + assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") + assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") + assert.Equal(t, 7, len(c.Realms), "Number of realms not as expected") + assert.Equal(t, "TEST.GOKRB5", c.Realms[6].Realm, "[realm] realm name not as expectd") + assert.Equal(t, []string{"cndc.test.gokrb5"}, c.Realms[6].AdminServer, "[realm] Admin_server not as expectd") + assert.Equal(t, []string{"cndc.test.gokrb5"}, c.Realms[6].KPasswdServer, "[realm] Kpasswd_server not as expectd") + assert.Equal(t, "test.gokrb5", c.Realms[6].DefaultDomain, "[realm] Default_domain not as expectd") + assert.Equal(t, []string{"cndc.test.gokrb5:88"}, c.Realms[6].KDC, "[realm] Kdc not as expectd") + +} + +func TestLoadinclude(t *testing.T) { + t.Parallel() + cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5inc.conf") + defer os.Remove(cf.Name()) + incf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5include") + defer os.Remove(incf.Name()) + incf.WriteString(krb5Include) + krb5ContentsI := krb5ConfIHead + fmt.Sprintf("include %s\n", incf.Name()) + krb5ConfI + cf.WriteString(krb5ContentsI) + + c, err := Load(cf.Name()) + if err != nil { + t.Fatalf("Error loading config: %v", err) + } + + assert.Equal(t, "TEST.GOKRB5", c.LibDefaults.DefaultRealm, "[libdefaults] default_realm not as expected") + assert.Equal(t, false, c.LibDefaults.DNSLookupRealm, "[libdefaults] dns_lookup_realm not as expected") + assert.Equal(t, false, c.LibDefaults.DNSLookupKDC, "[libdefaults] dns_lookup_kdc not as expected") + assert.Equal(t, time.Duration(10)*time.Hour, c.LibDefaults.TicketLifetime, "[libdefaults] Ticket lifetime not as expected") + assert.Equal(t, true, c.LibDefaults.Forwardable, "[libdefaults] forwardable not as expected") + assert.Equal(t, "FILE:/etc/krb5.keytab", c.LibDefaults.DefaultKeytabName, "[libdefaults] default_keytab_name not as expected") + assert.Equal(t, "FILE:/home/gokrb5/client.keytab", c.LibDefaults.DefaultClientKeytabName, "[libdefaults] default_client_keytab_name not as expected") + assert.Equal(t, []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96"}, c.LibDefaults.DefaultTktEnctypes, "[libdefaults] default_tkt_enctypes not as expected") + + assert.Equal(t, 3, len(c.Realms), "Number of realms not as expected") + assert.Equal(t, "TEST.GOKRB5", c.Realms[0].Realm, "[realm] realm name not as expectd") + assert.Equal(t, []string{"10.80.88.88:749"}, c.Realms[0].AdminServer, "[realm] Admin_server not as expectd") + assert.Equal(t, []string{"10.80.88.88:464"}, c.Realms[0].KPasswdServer, "[realm] Kpasswd_server not as expectd") + assert.Equal(t, "test.gokrb5", c.Realms[0].DefaultDomain, "[realm] Default_domain not as expectd") + assert.Equal(t, []string{"10.80.88.88:88", "assume.port.num:88", "some.other.port:1234", "10.80.88.88:88"}, c.Realms[0].KDC, "[realm] Kdc not as expectd") + assert.Equal(t, []string{"kerberos.example.com:88", "kerberos-1.example.com:88"}, c.Realms[1].KDC, "[realm] Kdc not as expectd") + assert.Equal(t, []string{"kerberos.example.com"}, c.Realms[1].AdminServer, "[realm] Admin_server not as expectd") + + assert.Equal(t, "TEST.GOKRB5", c.DomainRealm[".test.gokrb5"], "Domain to realm mapping not as expected") + assert.Equal(t, "TEST.GOKRB5", c.DomainRealm["test.gokrb5"], "Domain to realm mapping not as expected") + +} + func TestLoad(t *testing.T) { t.Parallel() cf, _ := ioutil.TempFile(os.TempDir(), "TEST-gokrb5-krb5.conf") diff --git a/config/testkrb5.conf.d/crypto-policies b/config/testkrb5.conf.d/crypto-policies new file mode 100644 index 00000000..c25d7c66 --- /dev/null +++ b/config/testkrb5.conf.d/crypto-policies @@ -0,0 +1,2 @@ +[libdefaults] +permitted_enctypes = aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha384-192 camellia256-cts-cmac aes128-cts-hmac-sha1-96 aes128-cts-hmac-sha256-128 camellia128-cts-cmac diff --git a/config/testkrb5.conf.d/fedoraproject_org b/config/testkrb5.conf.d/fedoraproject_org new file mode 100644 index 00000000..4f2dd105 --- /dev/null +++ b/config/testkrb5.conf.d/fedoraproject_org @@ -0,0 +1,11 @@ +[realms] + FEDORAPROJECT.ORG = { + kdc = https://id.fedoraproject.org/KdcProxy + pkinit_anchors = FILE:/etc/pki/ipa/fedoraproject_ipa_ca.crt + } +[domain_realm] + .fedoraproject.org = FEDORAPROJECT.ORG + fedoraproject.org = FEDORAPROJECT.ORG + .centos.org = FEDORAPROJECT.ORG + centos.org = FEDORAPROJECT.ORG + diff --git a/config/testkrb5.conf.d/puppet_FOTO.FR.conf b/config/testkrb5.conf.d/puppet_FOTO.FR.conf new file mode 100644 index 00000000..a35eb692 --- /dev/null +++ b/config/testkrb5.conf.d/puppet_FOTO.FR.conf @@ -0,0 +1,17 @@ +; Puppet managed file for realm FOTO.FR +; Files matching /etc/krb5.conf.d/puppet_* will be purged if +; no longer configured in puppet. + +[domain_realm] +.foto.fr = FOTO.FR + +[realms] +IN2P3.FR = { + default_domain = foto.fr + kpasswd_server = kerbadmin.foto.fr + admin_server = kerbadmin.foto.fr + kdc = kerb441.foto.fr + kdc = kerb442.foto.fr + kdc = kerb443.foto.fr +} + diff --git a/config/testkrb5.conf.d/puppet_MEP.TOTO.AC.UK.conf b/config/testkrb5.conf.d/puppet_MEP.TOTO.AC.UK.conf new file mode 100644 index 00000000..4e426023 --- /dev/null +++ b/config/testkrb5.conf.d/puppet_MEP.TOTO.AC.UK.conf @@ -0,0 +1,18 @@ +; Puppet totoaged file for realm MEP.TOTO.AC.UK +; Files matching /etc/krb5.conf.d/puppet_* will be purged if +; no longer configured in puppet. + +[domain_realm] +.mep.toto.ac.uk = MEP.TOTO.AC.UK + +[realms] +MEP.TOTO.AC.UK = { + default_domain = mep.toto.ac.uk + kpasswd_server = afs444.mep.toto.ac.uk + admin_server = afs4444.mep.toto.ac.uk + kdc = afs441.mep.toto.ac.uk + kdc = afs442.mep.toto.ac.uk + kdc = afs443.mep.toto.ac.uk + kdc = afs444.mep.toto.ac.uk +} + diff --git a/config/testkrb5.conf.d/puppet_MOTO.HU.conf b/config/testkrb5.conf.d/puppet_MOTO.HU.conf new file mode 100644 index 00000000..52c91ad4 --- /dev/null +++ b/config/testkrb5.conf.d/puppet_MOTO.HU.conf @@ -0,0 +1,12 @@ +; Puppet managed file for realm MOTO.HU +; Files matching /etc/krb5.conf.d/puppet_* will be purged if +; no longer configured in puppet. + +[domain_realm] + +[realms] +MOTO.HU = { + admin_server = kerberos.moto.hu + kdc = kerberos.moto.hu +} + diff --git a/config/testkrb5.conf.d/puppet_TOTO.GOV.conf b/config/testkrb5.conf.d/puppet_TOTO.GOV.conf new file mode 100644 index 00000000..a73e0a42 --- /dev/null +++ b/config/testkrb5.conf.d/puppet_TOTO.GOV.conf @@ -0,0 +1,23 @@ +; Puppet managed file for realm TOTO.GOV +; Files matching /etc/krb5.conf.d/puppet_* will be purged if +; no longer configured in puppet. + +[domain_realm] +.toto.gov = TOTO.GOV + +[realms] +FNAL.GOV = { + default_domain = toto.gov + admin_server = krbtoto-admin.toto.gov + kdc = krbtoto-fcc33.toto.gov:88 + kdc = krbtoto-42.toto.gov:88 + kdc = krbtoto-43.toto.gov:88 + kdc = krbtoto-41.toto.gov:88 + kdc = krbtoto-44.toto.gov:88 + kdc = krbtoto-enstr.toto.gov:88 + kdc = krbtoto-fg32.toto.gov:88 + kdc = krbtoto-cms88.toto.gov:88 + kdc = krbtoto-cms04.toto.gov:88 + kdc = krbtoto-d033on.toto.gov:88 +} + diff --git a/config/testkrb5.conf.d/puppet_default_ccache_name b/config/testkrb5.conf.d/puppet_default_ccache_name new file mode 100644 index 00000000..f08e427d --- /dev/null +++ b/config/testkrb5.conf.d/puppet_default_ccache_name @@ -0,0 +1,3 @@ +; Puppet managed +[libdefaults] + default_ccache_name = KEYRING:persistent:%{uid} diff --git a/config/testkrb5.conf.d/puppet_hostrealm.conf b/config/testkrb5.conf.d/puppet_hostrealm.conf new file mode 100644 index 00000000..98395c34 --- /dev/null +++ b/config/testkrb5.conf.d/puppet_hostrealm.conf @@ -0,0 +1,3 @@ +; Puppet managed +[domain_realm] + aiadm01.test.gokrb5 = TEST.GOKRB5 diff --git a/config/testkrb5.conf.d/stg_fedoraproject_org b/config/testkrb5.conf.d/stg_fedoraproject_org new file mode 100644 index 00000000..940422ec --- /dev/null +++ b/config/testkrb5.conf.d/stg_fedoraproject_org @@ -0,0 +1,8 @@ +[realms] + STG.FEDORAPROJECT.ORG = { + kdc = https://id.stg.fedoraproject.org/KdcProxy + pkinit_anchors = FILE:/etc/pki/ipa/stg_fedoraproject_ipa_ca.crt + } +[domain_realm] + .stg.fedoraproject.org = STG.FEDORAPROJECT.ORG + stg.fedoraproject.org = STG.FEDORAPROJECT.ORG diff --git a/config/testkrb5.conf.d/test-defaults-dns_canon_host_fallback.conf b/config/testkrb5.conf.d/test-defaults-dns_canon_host_fallback.conf new file mode 100644 index 00000000..899e44c4 --- /dev/null +++ b/config/testkrb5.conf.d/test-defaults-dns_canon_host_fallback.conf @@ -0,0 +1,3 @@ +[libdefaults] +# dns_canonicalize_hostname = fallback + dns_canonicalize_hostname = false diff --git a/config/testkrb5.conf.d/test-defaults-testgokrb5.conf b/config/testkrb5.conf.d/test-defaults-testgokrb5.conf new file mode 100644 index 00000000..9be443e7 --- /dev/null +++ b/config/testkrb5.conf.d/test-defaults-testgokrb5.conf @@ -0,0 +1,21 @@ +[libdefaults] + default_realm = TEST.GOKRB5 + ticket_lifetime = 25h + renew_lifetime = 120h + proxiable = true + rdns = true + default_client_keytab_name = FILE:/home/gokrb5/client.keytab + +[domain_realm] + .test.gokrb5 = TEST.GOKRB5 + +[appdefaults] + pkinit_pool = DIR:/etc/pki/tls/certs/ + pkinit_anchors = DIR:/etc/pki/tls/certs/ + pam = { + external = true + krb4_convert = false + krb4_convert_524 = false + krb4_use_as_req = false + ticket_lifetime = 25h + } diff --git a/config/testkrb5.conf.d/test-realm-testgokrb5.conf b/config/testkrb5.conf.d/test-realm-testgokrb5.conf new file mode 100644 index 00000000..0ef5ac92 --- /dev/null +++ b/config/testkrb5.conf.d/test-realm-testgokrb5.conf @@ -0,0 +1,14 @@ +[realms] + TEST.GOKRB5 = { + default_domain = test.gokrb5 + kpasswd_server = cndc.test.gokrb5 + admin_server = cndc.test.gokrb5 + kdc = cndc.test.gokrb5 + dns_lookup_kdc = false + +# v4_name_convert = { +# host = { +# rcmd = host +# } +# } + }