summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Krogh <markus@nordu.net>2019-02-07 11:11:53 +0100
committerMarkus Krogh <markus@nordu.net>2019-02-07 11:11:53 +0100
commit7f437db53b49339615bbad9813e8beee522de493 (patch)
tree41fc0b15ea5d91141620d30ec8194845b23da3ab
parent51d49d000e012a99726057998dfb9fcc6c3e743c (diff)
Use kinit and kadmin directly rather than perl script
-rw-r--r--Dockerfile13
-rw-r--r--README.md7
-rw-r--r--kdc.go93
-rw-r--r--main.go20
-rw-r--r--requirements.txt4
-rwxr-xr-xscripts/create-kdc-principal.pl18
6 files changed, 94 insertions, 61 deletions
diff --git a/Dockerfile b/Dockerfile
index 26804f3..2261f57 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
diff --git a/README.md b/README.md
index d7ca4a9..88e12e1 100644
--- a/README.md
+++ b/README.md
@@ -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
```
diff --git a/kdc.go b/kdc.go
index 81dded1..0c35a16 100644
--- a/kdc.go
+++ b/kdc.go
@@ -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
+}
diff --git a/main.go b/main.go
index d73db97..1286e76 100644
--- a/main.go
+++ b/main.go
@@ -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);
-}