diff options
author | Markus Krogh <markus@nordu.net> | 2019-02-07 11:11:53 +0100 |
---|---|---|
committer | Markus Krogh <markus@nordu.net> | 2019-02-07 11:11:53 +0100 |
commit | 7f437db53b49339615bbad9813e8beee522de493 (patch) | |
tree | 41fc0b15ea5d91141620d30ec8194845b23da3ab | |
parent | 51d49d000e012a99726057998dfb9fcc6c3e743c (diff) |
Use kinit and kadmin directly rather than perl script
-rw-r--r-- | Dockerfile | 13 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | kdc.go | 93 | ||||
-rw-r--r-- | main.go | 20 | ||||
-rw-r--r-- | requirements.txt | 4 | ||||
-rwxr-xr-x | scripts/create-kdc-principal.pl | 18 |
6 files changed, 94 insertions, 61 deletions
@@ -1,19 +1,18 @@ FROM golang:1.11 as build WORKDIR /go/src/pwman -RUN go get -d -v gopkg.in/ldap.v2 github.com/gorilla/csrf gopkg.in/jcmturner/gokrb5.v5/client gopkg.in/jcmturner/gokrb5.v5/config github.com/namsral/flag +RUN go get -d -v gopkg.in/ldap.v2 github.com/gorilla/csrf github.com/namsral/flag COPY *.go ./ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o pwman . -#FROM alpine:latest -#RUN apk --no-cache add ca-certificates -FROM ubuntu:18.04 -RUN apt-get update && \ - apt-get install -y libheimdal-kadm5-perl ca-certificates +FROM alpine:3.9 +RUN apk --no-cache add ca-certificates heimdal +#FROM ubuntu:18.04 +#RUN apt-get update && \ +# apt-get install -y libheimdal-kadm5-perl ca-certificates WORKDIR /opt COPY --from=build /go/src/pwman/pwman /usr/local/bin/ COPY krb5.conf /etc/krb5.conf -COPY scripts scripts COPY static static COPY templates templates ENV KRB5_CONF=/etc/krb5.conf @@ -39,12 +39,13 @@ The only required variable is the `LDAP_PASSWORD` - LDAP_PORT - defaults to `636` - LDAP_USER - defaults to `cn=admin,dc=nordu,dc=net` - LDAP_PASSWORD -- CHANGEPW_SCRIPT - `/opt/scripts/create-kdc-principal.pl` +- KRB5_PRINCIPAL - defaults to `pwman` +- KRB5_KEYTAB - defaults to `keytabs/pwman.keytab` +- KRB5_REALM - defaults to `NORDU.NET` - CSRF_SECRET - random 32 characters (including specials) - ADDRESS - sets the address the pwman server will listen on - `:3000` - BASE_PATH - Pwman should reside under e.g. `/sso` - PWNED - path to pwned passwords v2 file -- KRB5_CONFIG - path to krb5.conf file Primarily development variables: @@ -67,7 +68,7 @@ For rapid testing that does not need to build docker images you can choose to ju docker-compose up # In another (hacky since it is not under gopath) -go run !(*_test).go -ldap-password secretpw -ldap-port 6636 -ldap-ssl-skip-verify -csrf-insecure -krb5-config dev/krb5.conf -changepw-script data/pwman/log-principal.pl -base-path /dev +go run !(*_test).go -ldap-password secretpw -ldap-port 6636 -ldap-ssl-skip-verify -csrf-insecure -base-path /dev # You can now access it on http://localhost/dev ``` @@ -2,12 +2,18 @@ package main import ( "fmt" - "gopkg.in/jcmturner/gokrb5.v5/client" - "gopkg.in/jcmturner/gokrb5.v5/config" "log" + "os" "os/exec" + "strings" ) +type Krb5Conf struct { + Principal string + Keytab string + Realm string +} + var suffixMap map[string]string = map[string]string{ "SSO": "", "EDUROAM": "/ppp", @@ -25,42 +31,83 @@ func CheckDuplicatePw(username, password string) error { } func checkKerberosDuplicatePw(suffix, username, password string) error { - principal := username + suffixMap[suffix] + // KRB5_CONFIG env for heimdal-kinit + // KRB5CCNAME for setting the cache name + + principal := fmt.Sprintf("%s%s@%s", username, suffixMap[suffix], pwman.Krb5Conf.Realm) + + cmd := exec.Command("kinit", "--password-file=STDIN", principal) + cmd.Env = append(os.Environ()) + cmd.Stdin = strings.NewReader(password) + + out, err := cmd.CombinedOutput() - config, err := config.Load(pwman.Krb5Conf) - kclient := client.NewClientWithPassword(principal, "NORDU.NET", password) - kclient.WithConfig(config) - err = kclient.Login() if err != nil { - // error either means bad password or no connection etc. - if containsEither(err.Error(), "KDC_ERR_PREAUTH", "Decrypting_Error", "KDC_ERR_C_PRINCIPAL_UNKNOWN") { - // Password did not match + // everything is fine, it should fail + if containsEither(string(out), "kinit: Password incorrect", "krb5_get_init_creds") { return nil } - fmt.Println("ERROR", err) - return fmt.Errorf("Error while checking %s password for duplicate, got error: %v", suffix, err) + return fmt.Errorf("Error while checking %s password for duplicate, got error: %v, Output: %s", suffix, err, out) } + + // Run kdestroy to log out + kdest := exec.Command("kdestroy") + kdest.Run() + return fmt.Errorf("Password already used with: %s account", suffix) } func ChangeKerberosPw(suffix, username, new_password string) error { - kerberos_uid := fmt.Sprintf("%s%s", username, suffixMap[suffix]) - // call script - cmd := exec.Command(pwman.ChangePwScript) - stdin, err := cmd.StdinPipe() + conf := pwman.Krb5Conf + kerberos_uid := fmt.Sprintf("%s%s@%s", username, suffixMap[suffix], conf.Realm) + + err := ensureCreatedKerberosPrincipal(kerberos_uid) + if err != nil { - return fmt.Errorf("Unable to open pipe for kerberos script: %v", err) + return err } - go func() { - defer stdin.Close() - fmt.Fprintf(stdin, "%s@NORDU.NET %s", kerberos_uid, new_password) - }() + cmd := exec.Command("kadmin", "-K", conf.Keytab, "-p", conf.Principal, "cpw", "-p", new_password, kerberos_uid) out, err := cmd.CombinedOutput() + if err != nil { - log.Println("ERROR", "Error running change password script, got error:", err, "with script output:", string(out)) - return fmt.Errorf("Error running change password script, got error: %v", err) + log.Println("ERROR", "Error runing kadmin for change password, got error:", err, "with script output:", string(out)) + return fmt.Errorf("Error could not change password, got error: %v", err) } return nil } + +func ensureCreatedKerberosPrincipal(principal string) error { + conf := pwman.Krb5Conf + exists, err := checkKerberosPrincipalExists(principal) + + if err != nil { + return err + } + + if !exists { + cmd := exec.Command("kadmin", "-K", conf.Keytab, "-p", conf.Principal, "add", "--use-defaults", "--random-password", principal) + out, err := cmd.CombinedOutput() + + if err != nil { + fmt.Errorf("Could not create principal %s, got error: %v, output: %s", principal, err, out) + } + } + return nil +} + +func checkKerberosPrincipalExists(principal string) (bool, error) { + conf := pwman.Krb5Conf + cmd := exec.Command("kadmin", "-K", conf.Keytab, "-p", conf.Principal, "get", principal) + // get markus/ppp: Principal does not exist + out, err := cmd.CombinedOutput() + + if err != nil { + if containsEither(string(out), "Principal does not exist") { + return false, nil + } + return false, fmt.Errorf("Could not check if principal %s exists, got error: %v, output: %s", principal, err, out) + } + return true, nil +} @@ -14,8 +14,7 @@ import ( type PwmanServer struct { LdapInfo *LdapInfo PwnedDBFile string - Krb5Conf string - ChangePwScript string + Krb5Conf *Krb5Conf RemoteUserHeader string BasePath string LogoutUrl string @@ -26,17 +25,26 @@ var pwman *PwmanServer const csrf_base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._#%!&:;?+{}[]" func main() { - var ldapServer, ldapUser, ldapPassword, pwnedFile, krb5Conf, changePwScript, csrfSecret, serverAddr, basePath, logoutUrl string + var ldapServer, ldapUser, ldapPassword, pwnedFile, krb5Principal, krb5Keytab, krb5Realm, csrfSecret, serverAddr, basePath, logoutUrl string var ldapPort int var ldapSkipSSLVerify, csrfInsecure, gennerateCsrfKey bool + + // LDAP flag.StringVar(&ldapServer, "ldap-server", "localhost", "the ldap server address") flag.IntVar(&ldapPort, "ldap-port", 636, "the ldap server port") flag.BoolVar(&ldapSkipSSLVerify, "ldap-ssl-skip-verify", false, "Should the ssl certificate of the ldap server be verfied") flag.StringVar(&ldapUser, "ldap-user", "cn=admin,dc=nordu,dc=net", "An ldap user that can change user attributes") flag.StringVar(&ldapPassword, "ldap-password", "", "Ldap user password") + + // PWNED flag.StringVar(&pwnedFile, "pwned", "./pwned/pwned-passwords-ordered.txt", "Path to the pwned passwords list") - flag.StringVar(&krb5Conf, "krb5-config", "./krb5.conf", "Path to kerberos config file") - flag.StringVar(&changePwScript, "changepw-script", "./scripts/create-kdc-principal.pl", "Path to the change password script") + + // KRB5 + flag.StringVar(&krb5Principal, "krb5-principal", "pwman", "The kerberos principal pwman should use for changes") + flag.StringVar(&krb5Keytab, "krb5-keytab", "keytabs/pwman.keytab", "The kerberos keytab that pwman should use") + flag.StringVar(&krb5Realm, "krb5-realm", "NORDU.NET", "The kerberos realm to use") + + // PWMAN flag.StringVar(&csrfSecret, "csrf-secret", "", "Specify csrf 32 char secret") flag.StringVar(&serverAddr, "address", ":3000", "Server address to listen on") flag.StringVar(&basePath, "base-path", "", "A base path that pwman lives under e.g. /sso") @@ -55,12 +63,12 @@ func main() { } ldapInfo := &LdapInfo{Server: ldapServer, Port: ldapPort, SSLSkipVerify: ldapSkipSSLVerify, User: ldapUser, Password: ldapPassword} + krb5Conf := &Krb5Conf{Principal: krb5Principal, Keytab: krb5Keytab, Realm: krb5Realm} pwman = &PwmanServer{ LdapInfo: ldapInfo, PwnedDBFile: pwnedFile, Krb5Conf: krb5Conf, - ChangePwScript: changePwScript, RemoteUserHeader: "X-Remote-User", BasePath: basePath, LogoutUrl: logoutUrl, diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index de0f057..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -uwsgi -Django==1.11 -python-ldap -pexpect diff --git a/scripts/create-kdc-principal.pl b/scripts/create-kdc-principal.pl deleted file mode 100755 index a88c96c..0000000 --- a/scripts/create-kdc-principal.pl +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env perl - -use Heimdal::Kadm5 qw(/KADM5_/); - -$client = Heimdal::Kadm5::Client->new('RaiseErrors'=>1,'Principal'=>'shibboleth-idp@NORDU.NET',Keytab=>'/opt/keytabs/pwman.keytab'); -my $line = <STDIN>; -my ($user,$pass) = split('\s+',$line); -die "Missing parameters on stdin\n" unless ($user and $pass); -my ($local,$domain) = split('\@',$user); - -my $pn = "$local\@NORDU.NET"; -my $p = $client->getPrincipal($pn); -unless ($p) { - $p = $client->makePrincipal($pn); - $client->createPrincipal($p,$pass); -} else { - $client->changePassword($pn,$pass); -} |