diff --git a/Makefile b/Makefile index c1b9024..3b9a2dc 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ endif BINARY=keymaster # These are the values we want to pass for Version and BuildTime -VERSION?=1.15.5 +VERSION?=1.16.0 DEFAULT_HOST?= VERSION_FLAVOUR?= EXTRA_LDFLAGS?= diff --git a/cmd/keymasterd/app.go b/cmd/keymasterd/app.go index 8d883c9..ba70234 100644 --- a/cmd/keymasterd/app.go +++ b/cmd/keymasterd/app.go @@ -794,6 +794,15 @@ func (state *RuntimeState) getUsernameIfKeymasterSigned(VerifiedChains [][]*x509 if err != nil { return "", time.Time{}, err } + userPubKeyFP, err := getKeyFingerprint(chain[0].PublicKey) + if err != nil { + return "", time.Time{}, err + } + for _, revokedKeyFP := range state.Config.DenyTrustData.KeyDenyFPsshSha256 { + if userPubKeyFP == revokedKeyFP { + return "", time.Time{}, fmt.Errorf("revoked key with FP:%s", revokedKeyFP) + } + } for _, key := range state.KeymasterPublicKeys { fp, err := getKeyFingerprint(key) if err != nil { @@ -869,36 +878,45 @@ func (state *RuntimeState) checkAuth(w http.ResponseWriter, r *http.Request, req state.logger.Debugf(3, "looks like authtype tls keymaster or ip cert, r.tls=%+v", r.TLS) if len(r.TLS.VerifiedChains) > 0 { - if (requiredAuthType & AuthTypeKeymasterX509) != 0 { - tlsAuthUser, notBefore, err := - state.getUsernameIfKeymasterSigned(r.TLS.VerifiedChains) - if err == nil && tlsAuthUser != "" { - return &authInfo{ - AuthType: AuthTypeKeymasterX509, - IssuedAt: notBefore, - Username: tlsAuthUser, - }, nil - } + var authData authInfo + tlsAuthUser, notBefore, err := + state.getUsernameIfKeymasterSigned(r.TLS.VerifiedChains) + if err == nil && tlsAuthUser != "" { + state.logger.Debugf(4, "Auth, Is keymastercert") + authData.AuthType = authData.AuthType | AuthTypeKeymasterX509 + authData.IssuedAt = notBefore + authData.Username = tlsAuthUser } if (requiredAuthType & AuthTypeIPCertificate) != 0 { clientName, notBefore, userErr, err := state.getUsernameIfIPRestricted(r.TLS.VerifiedChains, r) - if userErr != nil { - state.writeFailureResponse(w, r, http.StatusForbidden, - fmt.Sprintf("%s", userErr)) - return nil, userErr + // if not keymasterd cert AND not ipcert either then we return + // more explicit errors + if authData.Username == "" { + state.logger.Printf("after eval, but username is empty") + if userErr != nil { + state.writeFailureResponse(w, r, http.StatusForbidden, + fmt.Sprintf("%s", userErr)) + return nil, userErr + } + if err != nil { + state.writeFailureResponse(w, r, + http.StatusInternalServerError, "") + return nil, err + } } - if err != nil { - state.writeFailureResponse(w, r, - http.StatusInternalServerError, "") - return nil, err + + if err == nil && userErr == nil { + authData.AuthType = authData.AuthType | AuthTypeIPCertificate + authData.IssuedAt = notBefore + authData.Username = clientName } - return &authInfo{ - AuthType: AuthTypeIPCertificate, - IssuedAt: notBefore, - Username: clientName, - }, nil } + if authData.Username != "" { + state.logger.Debugf(4, "returning tls cert authinfo") + return &authData, nil + } + state.logger.Debugf(4, "NOT returning tls cert authinfo authData=%+v", authData) } } // Next we check for cookies @@ -1937,6 +1955,8 @@ func main() { serviceMux.HandleFunc(paths.VerifyAuthToken, runtimeState.VerifyAuthTokenHandler) } + serviceMux.HandleFunc(getRoleRequestingPath, runtimeState.roleRequetingCertGenHandler) + serviceMux.HandleFunc(refreshRoleRequestingCertPath, runtimeState.refreshRoleRequestingCertGenHandler) serviceMux.HandleFunc("/", runtimeState.defaultPathHandler) cfg := &tls.Config{ diff --git a/cmd/keymasterd/config.go b/cmd/keymasterd/config.go index bd45653..15809cc 100644 --- a/cmd/keymasterd/config.go +++ b/cmd/keymasterd/config.go @@ -91,6 +91,7 @@ type baseConfig struct { SecsBetweenDependencyChecks int `yaml:"secs_between_dependency_checks"` AutomationUserGroups []string `yaml:"automation_user_groups"` AutomationUsers []string `yaml:"automation_users"` + AutomationAdmins []string `yaml:"automation_admins"` DisableUsernameNormalization bool `yaml:"disable_username_normalization"` EnableLocalTOTP bool `yaml:"enable_local_totp"` EnableBootstrapOTP bool `yaml:"enable_bootstrapotp"` @@ -189,6 +190,10 @@ type SymantecVIPConfig struct { RequireAppAproval bool `yaml:"require_app_approval"` } +type DenyKeyConfig struct { + KeyDenyFPsshSha256 []string `yaml:"key_deny_list_ssh_sha256"` +} + type AppConfigFile struct { Base baseConfig AwsCerts awsCertsConfig `yaml:"aws_certs"` @@ -202,6 +207,7 @@ type AppConfigFile struct { OpenIDConnectIDP OpenIDConnectIDPConfig `yaml:"openid_connect_idp"` SymantecVIP SymantecVIPConfig ProfileStorage ProfileStorageConfig + DenyTrustData DenyKeyConfig } const ( diff --git a/cmd/keymasterd/roleRequestingCert.go b/cmd/keymasterd/roleRequestingCert.go new file mode 100644 index 0000000..7fee759 --- /dev/null +++ b/cmd/keymasterd/roleRequestingCert.go @@ -0,0 +1,348 @@ +package main + +import ( + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "net" + "net/http" + "time" + + "github.com/Cloud-Foundations/keymaster/lib/certgen" + "github.com/Cloud-Foundations/keymaster/lib/instrumentedwriter" + "github.com/Cloud-Foundations/keymaster/lib/util" +) + +const getRoleRequestingPath = "/v1/getRoleRequestingCert" +const refreshRoleRequestingCertPath = "/v1/refreshRoleRequestingCert" +const maxRoleRequestingCertDuration = time.Hour * 24 * 45 + +type roleRequestingCertGenParams struct { + Role string + Duration time.Duration + RequestorNetblocks []net.IPNet + TargetNetblocks []net.IPNet + UserPub interface{} +} + +func (state *RuntimeState) parseRoleCertGenParams(r *http.Request) (*roleRequestingCertGenParams, error, error) { + state.logger.Debugf(3, "parseRoleCertGenParams: Got client POST connection") + err := r.ParseForm() + if err != nil { + state.logger.Println(err) + return nil, err, nil + } + var rvalue roleRequestingCertGenParams + /* + Role name: role + Public Key (PEM): pubkey + Requestor (Hypervisor) netblock: requestor_netblock + Target (VM) netblock: target_netblock + Optional duration: duration (i.e. 730h: :golang: time format) + */ + // Role/Identity + roleName := r.PostForm.Get("identity") + if roleName == "" { + return nil, fmt.Errorf("Missing identity parameter"), nil + } + ok, err := state.isAutomationUser(roleName) + if err != nil { + return nil, nil, err + } + if !ok { + return nil, fmt.Errorf("requested role is not automation user"), nil + } + rvalue.Role = roleName + + //Duration + rvalue.Duration = maxRoleRequestingCertDuration + + //RequestorNetblocks + requestorNetblockStrings, ok := r.PostForm["requestor_netblock"] + if !ok { + return nil, fmt.Errorf("missing required requestor_netblock param"), nil + } + for _, netBlock := range requestorNetblockStrings { + _, parsedNetBlock, err := net.ParseCIDR(netBlock) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("invalid netblock"), nil + } + rvalue.RequestorNetblocks = append(rvalue.RequestorNetblocks, *parsedNetBlock) + } + //TargetNetblocks + targetNetblockStrings, ok := r.PostForm["target_netblock"] + if !ok { + return nil, fmt.Errorf("missing required requestor_netblock param"), nil + } + for _, netBlock := range targetNetblockStrings { + _, parsedNetBlock, err := net.ParseCIDR(netBlock) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("invalid netblock %s", netBlock), nil + } + rvalue.TargetNetblocks = append(rvalue.TargetNetblocks, *parsedNetBlock) + } + + // publickey + b64pubkey := r.PostForm.Get("pubkey") + if b64pubkey == "" { + return nil, fmt.Errorf("Missing pubkey parameter"), nil + } + pkixDerPub, err := base64.RawURLEncoding.DecodeString(b64pubkey) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("Invalid encoding for pubkey"), nil + } + userPub, err := x509.ParsePKIXPublicKey(pkixDerPub) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("pubkey is not valid PKIX public key"), nil + } + validKey, err := certgen.ValidatePublicKeyStrength(userPub) + if err != nil { + return nil, nil, err + } + if !validKey { + return nil, fmt.Errorf("Invalid File, Check Key strength/key type"), nil + } + rvalue.UserPub = userPub + + return &rvalue, nil, nil +} + +func (state *RuntimeState) isAutomationAdmin(user string) bool { + isAdmin := state.IsAdminUser(user) + if isAdmin { + return true + } + for _, adminUser := range state.Config.Base.AutomationAdmins { + if user == adminUser { + return true + } + } + return false +} + +func (state *RuntimeState) roleRequetingCertGenHandler(w http.ResponseWriter, r *http.Request) { + var signerIsNull bool + state.Mutex.Lock() + signerIsNull = (state.Signer == nil) + state.Mutex.Unlock() + + //local sanity tests + if signerIsNull { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + state.logger.Printf("Signer not loaded") + return + } + + authData, err := state.checkAuth(w, r, + state.getRequiredWebUIAuthLevel()|AuthTypeKeymasterX509) + if err != nil { + state.logger.Debugf(1, "%v", err) + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + return + } + w.(*instrumentedwriter.LoggingWriter).SetUsername(authData.Username) + + // TODO: this should be a different check, for now keep it to automationadmin users + if !state.isAutomationAdmin(authData.Username) { + state.writeFailureResponse(w, r, http.StatusForbidden, + "Not an admin user") + return + } + + // TODO: maybe add a check to ensure no self-replication + // We dont anything to request a rolerequesting role for itself + + /// Now we parse the inputs + if r.Method != "POST" { + state.writeFailureResponse(w, r, http.StatusMethodNotAllowed, "") + return + } + params, userError, err := state.parseRoleCertGenParams(r) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + return + } + if userError != nil { + state.writeFailureResponse(w, r, http.StatusBadRequest, + userError.Error()) + return + } + pemCert, cert, err := state.withParamsGenerateRoleRequestingCert(params) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + state.logger.Printf("Error generating cert", err) + return + } + clientIpAddress := util.GetRequestRealIp(r) + + w.Header().Set("Content-Disposition", `attachment; filename="roleRequstingCert.pem"`) + w.WriteHeader(200) + fmt.Fprintf(w, "%s", pemCert) + state.logger.Printf("Generated x509 role Requesting Certificate for %s (from %s). Serial: %s", + params.Role, clientIpAddress, cert.SerialNumber.String()) + + return + +} +func (state *RuntimeState) withParamsGenerateRoleRequestingCert(params *roleRequestingCertGenParams) (string, *x509.Certificate, error) { + signer, caCertDer, err := state.getSignerX509CAForPublic(params.UserPub) + if err != nil { + return "", nil, fmt.Errorf("Error Finding Cert for public key: %s\n data", err) + } + caCert, err := x509.ParseCertificate(caCertDer) + if err != nil { + return "", nil, fmt.Errorf("Cannot parse CA Der: %s\n data", err) + } + + derCert, err := certgen.GenIPRestrictedX509Cert(params.Role, params.UserPub, + caCert, signer, params.RequestorNetblocks, params.Duration, nil, nil) + + if err != nil { + return "", nil, fmt.Errorf("Cannot Generate x509cert: %s\n", err) + } + parsedCert, err := x509.ParseCertificate(derCert) + if err != nil { + return "", nil, fmt.Errorf("Cannot Parse Generated x509cert: %s\n", err) + } + + eventNotifier.PublishX509(derCert) + cert := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", + Bytes: derCert})) + return cert, parsedCert, nil + +} + +func (state *RuntimeState) parseRefreshRoleCertGenParams(authData *authInfo, r *http.Request) (*roleRequestingCertGenParams, error, error) { + state.logger.Debugf(4, "Got client POST connection") + err := r.ParseForm() + if err != nil { + state.logger.Println(err) + return nil, err, nil + } + state.logger.Debugf(4, "parseRefreshRoleCertGenParams past postform r=%+v", r) + + var rvalue roleRequestingCertGenParams + /* + Role name: role + Public Key (PEM): pubkey + Requestor (Hypervisor) netblock: requestor_netblock + Target (VM) netblock: target_netblock + Optional duration: duration (i.e. 730h: :golang: time format) + */ + // Role + identityName := authData.Username + if identityName == "" { + return nil, fmt.Errorf("Missing identity parameter"), nil + } + ok, err := state.isAutomationUser(identityName) + if err != nil { + return nil, nil, err + } + if !ok { + return nil, fmt.Errorf("requested role is not automation user"), nil + } + rvalue.Role = identityName + + //Duration + // TODO: actually parse to allow smaller valjues + rvalue.Duration = maxRoleRequestingCertDuration + + // publickey + b64pubkey := r.PostForm.Get("pubkey") + if b64pubkey == "" { + return nil, fmt.Errorf("Missing pubkey parameter"), nil + } + pkixDerPub, err := base64.RawURLEncoding.DecodeString(b64pubkey) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("Invalid encoding for pubkey"), nil + } + userPub, err := x509.ParsePKIXPublicKey(pkixDerPub) + if err != nil { + state.logger.Printf("%s", err) + return nil, fmt.Errorf("pubkey is not valid PKIX public key"), nil + } + validKey, err := certgen.ValidatePublicKeyStrength(userPub) + if err != nil { + return nil, nil, err + } + if !validKey { + return nil, fmt.Errorf("Invalid File, Check Key strength/key type"), nil + } + rvalue.UserPub = userPub + + // networks + if r.TLS == nil { + return nil, fmt.Errorf("MUST only come from certificate"), nil + } + if len(r.TLS.VerifiedChains) < 1 { + return nil, fmt.Errorf("MUST only come from certificate"), nil + } + userCert := r.TLS.VerifiedChains[0][0] + certNets, err := certgen.ExtractIPNetsFromIPRestrictedX509(userCert) + if err != nil { + return nil, nil, err + } + rvalue.RequestorNetblocks = certNets + return &rvalue, nil, nil +} + +func (state *RuntimeState) refreshRoleRequestingCertGenHandler(w http.ResponseWriter, r *http.Request) { + var signerIsNull bool + state.Mutex.Lock() + signerIsNull = (state.Signer == nil) + state.Mutex.Unlock() + //local sanity tests + if signerIsNull { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + state.logger.Printf("Signer not loaded") + return + } + + state.logger.Debugf(1, "refreshRoleRequestingCertGenHandler before auth") + authData, err := state.checkAuth(w, r, AuthTypeIPCertificate) + if err != nil { + state.logger.Debugf(1, "%v", err) + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + return + } + // TODO: we need to do denylist checks here against the cert/certkey + state.logger.Debugf(1, "refreshRoleRequestingCertGenHandler: authenticated") + + w.(*instrumentedwriter.LoggingWriter).SetUsername(authData.Username) + + /// Now we parse the inputs + if r.Method != "POST" { + state.writeFailureResponse(w, r, http.StatusMethodNotAllowed, "") + return + } + params, userError, err := state.parseRefreshRoleCertGenParams(authData, r) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + return + } + if userError != nil { + state.logger.Debugf(1, "refreshRoleRequestingCertGenHandler: error parsing params err=%s", userError) + state.writeFailureResponse(w, r, http.StatusBadRequest, + userError.Error()) + return + } + pemCert, cert, err := state.withParamsGenerateRoleRequestingCert(params) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + state.logger.Printf("Error generating cert", err) + return + } + clientIpAddress := util.GetRequestRealIp(r) + w.Header().Set("Content-Disposition", `attachment; filename="roleRequstingCert.pem"`) + w.WriteHeader(200) + fmt.Fprintf(w, "%s", pemCert) + state.logger.Printf("Generated x509 role Requesting Certificate for %s (from %s). Serial: %s", + params.Role, clientIpAddress, cert.SerialNumber.String()) + return +} diff --git a/cmd/keymasterd/roleRequestingCert_test.go b/cmd/keymasterd/roleRequestingCert_test.go new file mode 100644 index 0000000..3a4250e --- /dev/null +++ b/cmd/keymasterd/roleRequestingCert_test.go @@ -0,0 +1,292 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/Cloud-Foundations/keymaster/lib/webapi/v0/proto" +) + +func TestParseRoleCertGenParams(t *testing.T) { + state, passwdFile, err := setupValidRuntimeStateSigner(t) + if err != nil { + t.Fatal(err) + } + defer os.Remove(passwdFile.Name()) // clean up + + // + state.Config.Base.AutomationUsers = append(state.Config.Base.AutomationUsers, "role1") + state.Config.Base.AutomationAdmins = append(state.Config.Base.AutomationAdmins, "admin1") + + //first pass everything OK + + userPemBlock, _ := pem.Decode([]byte(testUserPEMPublicKey)) + b64public := base64.RawURLEncoding.EncodeToString(userPemBlock.Bytes) + + form := url.Values{} + form.Add("identity", "role1") + form.Add("requestor_netblock", "127.0.0.1/32") + form.Add("pubkey", b64public) + form.Add("target_netblock", "192.168.0.174/32") + + //form.Add("password", validPasswordConst) + + req, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + _, userErr, err := state.parseRoleCertGenParams(req) + if err != nil { + t.Fatal(err) + } + if userErr != nil { + t.Fatal(userErr) + } + + // now test with broken public key + form.Set("pubkey", "aGVsbG8gdGhpcyBpcyBzb21laGl0bmcK") + req2, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + req2.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req2.Header.Add("Content-Type", "application/x-www-form-urlencoded") + _, userErr, err = state.parseRoleCertGenParams(req2) + if err != nil { + t.Fatal(err) + } + if userErr == nil { + t.Fatal("should have failed because Public key is not valid") + } + +} + +func TestRoleRequetingCertGenHandler(t *testing.T) { + state, passwdFile, err := setupValidRuntimeStateSigner(t) + if err != nil { + t.Fatal(err) + } + defer os.Remove(passwdFile.Name()) // clean up + + // + state.Config.Base.AutomationUsers = append(state.Config.Base.AutomationUsers, "role1") + state.Config.Base.AutomationAdmins = append(state.Config.Base.AutomationAdmins, "admin1") + state.Config.Base.AllowedAuthBackendsForCerts = append(state.Config.Base.AllowedAuthBackendsForCerts, proto.AuthTypePassword) + state.Config.Base.AllowedAuthBackendsForWebUI = []string{"password"} + + userPemBlock, _ := pem.Decode([]byte(testUserPEMPublicKey)) + b64public := base64.RawURLEncoding.EncodeToString(userPemBlock.Bytes) + form := url.Values{} + form.Add("identity", "role1") + form.Add("requestor_netblock", "127.0.0.1/32") + form.Add("pubkey", b64public) + form.Add("target_netblock", "192.168.0.174/32") + + req, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + + cookieVal, err := state.setNewAuthCookie(nil, "admin1", AuthTypePassword) + if err != nil { + t.Fatal(err) + } + authCookie := http.Cookie{Name: authCookieName, Value: cookieVal} + req.AddCookie(&authCookie) + req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + rr, err := checkRequestHandlerCode(req, state.roleRequetingCertGenHandler, http.StatusOK) + if err != nil { + t.Fatal(err) + } + + resp := rr.Result() + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + // TODO: check body content is actually pem + + //now disable the role as automation use and it should fail + state.Config.Base.AutomationUsers = []string{} + req2, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + req2.AddCookie(&authCookie) + req2.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req2.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + _, err = checkRequestHandlerCode(req, state.roleRequetingCertGenHandler, http.StatusBadRequest) + if err != nil { + t.Fatal(err) + } + +} + +func TestRoleRequetingCertGenHandlerTLSAuth(t *testing.T) { + state, tmpdir, err := testCreateRuntimeStateWithBothCAs(t) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + state.Config.Base.AutomationUsers = append(state.Config.Base.AutomationUsers, "role1") + state.Config.Base.AutomationAdmins = append(state.Config.Base.AutomationAdmins, "admin1") + state.Config.Base.AllowedAuthBackendsForCerts = append(state.Config.Base.AllowedAuthBackendsForCerts, proto.AuthTypeIPCertificate) + + //Bob id not admin, should fail with forbidden + req := httptest.NewRequest("POST", getRoleRequestingPath, nil) + req.TLS, err = testMakeConnectionState("testdata/bob.pem", + "testdata/KeymasterCA.pem") + + _, err = checkRequestHandlerCode(req, state.roleRequetingCertGenHandler, http.StatusForbidden) + if err != nil { + t.Fatal(err) + } + + //alice is admin... but there is no data, it should fail + req = httptest.NewRequest("POST", getRoleRequestingPath, nil) + req.TLS, err = testMakeConnectionState("testdata/alice.pem", + "testdata/KeymasterCA.pem") + _, err = checkRequestHandlerCode(req, state.roleRequetingCertGenHandler, http.StatusBadRequest) + if err != nil { + t.Fatal(err) + } + // lets create valid inputs + userPemBlock, _ := pem.Decode([]byte(testUserPEMPublicKey)) + b64public := base64.RawURLEncoding.EncodeToString(userPemBlock.Bytes) + form := url.Values{} + form.Add("identity", "role1") + form.Add("requestor_netblock", "127.0.0.1/32") + form.Add("pubkey", b64public) + form.Add("target_netblock", "192.168.0.174/32") + + req2, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + req2.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req2.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req2.TLS, err = testMakeConnectionState("testdata/alice.pem", + "testdata/KeymasterCA.pem") + rr, err := checkRequestHandlerCode(req2, state.roleRequetingCertGenHandler, http.StatusOK) + if err != nil { + t.Fatal(err) + } + // TODO: check body response is actual cert to get a cert + resp := rr.Result() + pemData, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + block, _ := pem.Decode(pemData) + if block.Type != "CERTIFICATE" { + t.Fatalf("no CERTIFICATE found") + } + rrCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + req3, err := createKeyBodyRequest("POST", "/certgen/role1?type=x509", testUserPEMPublicKey, "") + if err != nil { + t.Fatal(err) + } + req3.RemoteAddr = "127.0.0.1:12345" + var fakePeerCertificates []*x509.Certificate + var fakeVerifiedChains [][]*x509.Certificate + fakePeerCertificates = append(fakePeerCertificates, rrCert) + fakeVerifiedChains = append(fakeVerifiedChains, fakePeerCertificates) + connectionState := &tls.ConnectionState{ + VerifiedChains: fakeVerifiedChains, + PeerCertificates: fakePeerCertificates} + req3.TLS = connectionState + _, err = checkRequestHandlerCode(req3, state.certGenHandler, http.StatusOK) + if err != nil { + t.Fatal(err) + } + // and now use the cert to +} + +func TestRefreshRoleRequetingCertGenHandler(t *testing.T) { + state, passwdFile, err := setupValidRuntimeStateSigner(t) + if err != nil { + t.Fatal(err) + } + defer os.Remove(passwdFile.Name()) // clean up + + // + state.Config.Base.AutomationUsers = append(state.Config.Base.AutomationUsers, "role1") + state.Config.Base.AutomationAdmins = append(state.Config.Base.AutomationAdmins, "admin1") + state.Config.Base.AllowedAuthBackendsForCerts = append(state.Config.Base.AllowedAuthBackendsForCerts, proto.AuthTypePassword) + state.Config.Base.AllowedAuthBackendsForWebUI = []string{"password"} + + userPub, err := getPubKeyFromPem(testUserPEMPublicKey) + if err != nil { + t.Fatal(err) + } + netblock := net.IPNet{ + IP: net.ParseIP("127.0.0.0"), + Mask: net.CIDRMask(8, 32), + } + netblock2 := net.IPNet{ + IP: net.ParseIP("10.0.0.0"), + Mask: net.CIDRMask(8, 32), + } + netblockList := []net.IPNet{netblock, netblock2} + + initialrrParams := roleRequestingCertGenParams{ + Role: "role1", + Duration: time.Hour, + RequestorNetblocks: netblockList, + UserPub: userPub, + } + _, rrcert, err := state.withParamsGenerateRoleRequestingCert(&initialrrParams) + if err != nil { + t.Fatal(err) + } + + userPemBlock, _ := pem.Decode([]byte(testUserPEMPublicKey)) + b64public := base64.RawURLEncoding.EncodeToString(userPemBlock.Bytes) + form := url.Values{} + form.Add("pubkey", b64public) + + req, err := http.NewRequest("POST", getRoleRequestingPath, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.RemoteAddr = "127.0.0.1:12345" + var fakePeerCertificates []*x509.Certificate + var fakeVerifiedChains [][]*x509.Certificate + fakePeerCertificates = append(fakePeerCertificates, rrcert) + fakeVerifiedChains = append(fakeVerifiedChains, fakePeerCertificates) + connectionState := &tls.ConnectionState{ + VerifiedChains: fakeVerifiedChains, + PeerCertificates: fakePeerCertificates} + req.TLS = connectionState + + //TODO add fail value + _, err = checkRequestHandlerCode(req, state.refreshRoleRequestingCertGenHandler, http.StatusOK) + if err != nil { + t.Fatal(err) + } + +} diff --git a/lib/certgen/iprestricted.go b/lib/certgen/iprestricted.go index 46f75c9..d2409c8 100644 --- a/lib/certgen/iprestricted.go +++ b/lib/certgen/iprestricted.go @@ -9,6 +9,8 @@ import ( "crypto/x509/pkix" "encoding/asn1" "errors" + "fmt" + //"log" "math/big" "net" @@ -26,7 +28,7 @@ type IpAdressFamily struct { var oidIPAddressDelegation = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 7} var ipV4FamilyEncoding = []byte{0, 1, 1} -//For now ipv4 only +// For now ipv4 only func encodeIpAddressChoice(netBlock net.IPNet) (asn1.BitString, error) { ones, bits := netBlock.Mask.Size() if bits != 32 { @@ -90,7 +92,6 @@ func decodeIPV4AddressChoice(encodedBlock asn1.BitString) (net.IPNet, error) { return netBlock, nil } -// type subjectPublicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier SubjectPublicKey asn1.BitString @@ -195,3 +196,37 @@ func VerifyIPRestrictedX509CertIP(userCert *x509.Certificate, remoteAddr string) } return false, nil } + +func ExtractIPNetsFromIPRestrictedX509(userCert *x509.Certificate) ([]net.IPNet, error) { + var extension *pkix.Extension = nil + var err error + for _, certExtension := range userCert.Extensions { + if certExtension.Id.Equal(oidIPAddressDelegation) { + extension = &certExtension + break + } + } + if extension == nil { + return nil, fmt.Errorf("extension not found") + } + var ipAddressFamilyList []IpAdressFamily + _, err = asn1.Unmarshal(extension.Value, &ipAddressFamilyList) + if err != nil { + return nil, err + } + var rvalue []net.IPNet + for _, addressList := range ipAddressFamilyList { + if !bytes.Equal(addressList.AddressFamily, ipV4FamilyEncoding) { + //continue + return nil, fmt.Errorf("We only support ipv4 netblocks") + } + for _, encodedNetblock := range addressList.Addresses { + decoded, err := decodeIPV4AddressChoice(encodedNetblock) + if err != nil { + return nil, err + } + rvalue = append(rvalue, decoded) + } + } + return rvalue, nil +} diff --git a/lib/certgen/iprestricted_test.go b/lib/certgen/iprestricted_test.go index f1d5fff..6c3c6bf 100644 --- a/lib/certgen/iprestricted_test.go +++ b/lib/certgen/iprestricted_test.go @@ -104,3 +104,36 @@ func TestGenIPRestrictedX509Cert(t *testing.T) { t.Fatal("should have failed extension not found") } } + +func TestExtractIPNetsFromIPRestrictedX509(t *testing.T) { + userPub, caCert, caPriv := setupX509Generator(t) + netblock := net.IPNet{ + IP: net.ParseIP("127.0.0.0"), + Mask: net.CIDRMask(8, 32), + } + netblock2 := net.IPNet{ + IP: net.ParseIP("10.0.0.0"), + Mask: net.CIDRMask(8, 32), + } + netblockList := []net.IPNet{netblock, netblock2} + derCert, err := GenIPRestrictedX509Cert("username", userPub, caCert, caPriv, netblockList, testDuration, nil, nil) + if err != nil { + t.Fatal(err) + } + cert, _, err := derBytesCertToCertAndPem(derCert) + if err != nil { + t.Fatal(err) + } + certNets, err := ExtractIPNetsFromIPRestrictedX509(cert) + if err != nil { + t.Fatal(err) + } + if len(certNets) != len(netblockList) { + t.Fatalf("lenghts should match") + } + for i, certNet := range certNets { + if certNet.String() != netblockList[i].String() { + t.Fatalf("nets dont match") + } + } +}