Skip to content

Commit

Permalink
Login MFA (#14025)
Browse files Browse the repository at this point in the history
* Login MFA

* ENT OSS segragation (#14088)

* Delete method id if not used in an MFA enforcement config (#14063)

* Delete an MFA methodID only if it is not used by an MFA enforcement config

* Fixing a bug: mfa/validate is an unauthenticated path, and goes through the handleLoginRequest path

* adding use_passcode field to DUO config (#14059)

* add changelog

* preventing replay attack on MFA passcodes (#14056)

* preventing replay attack on MFA passcodes

* using %w instead of %s for error

* Improve CLI command for login mfa (#14106)

CLI prints a warning message indicating the login request needs to get validated

* adding the validity period of a passcode to error messages (#14115)

* PR feedback

* duo to handle preventing passcode reuse

Co-authored-by: hghaf099 <[email protected]>
Co-authored-by: hamid ghaf <[email protected]>
  • Loading branch information
3 people authored Feb 17, 2022
1 parent 00c3e8f commit 2fe214f
Show file tree
Hide file tree
Showing 42 changed files with 5,481 additions and 233 deletions.
6 changes: 6 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,12 @@ func (c *Client) setNamespace(namespace string) {
c.headers.Set(consts.NamespaceHeaderName, namespace)
}

func (c *Client) ClearNamespace() {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.headers.Del(consts.NamespaceHeaderName)
}

// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
func (c *Client) Token() string {
Expand Down
3 changes: 3 additions & 0 deletions api/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
)

// Secret is the structure returned for every secret within Vault.
Expand Down Expand Up @@ -297,6 +298,8 @@ type SecretAuth struct {

LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`

MFARequirement *logical.MFARequirement `json:"mfa_requirement"`
}

// ParseSecret is used to parse a secret value from JSON from an io.Reader.
Expand Down
3 changes: 3 additions & 0 deletions changelog/14025.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
auth: Add support for single and two phase MFA to login endpoints.
```
5 changes: 5 additions & 0 deletions command/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ func (c *WriteCommand) Run(args []string) int {
return 0
}

if secret != nil && secret.Auth != nil && secret.Auth.MFARequirement != nil {
c.UI.Warn(wrapAtLength("A login request was issued that is subject to "+
"MFA validation. Please make sure to validate the login by sending another "+
"request to mfa/validate endpoint.") + "\n")
}
// Handle single field output
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/coreos/go-semver v0.3.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/denisenkom/go-mssqldb v0.12.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/docker/docker v20.10.10+incompatible
github.com/docker/go-connections v0.4.0
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8l
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba h1:p6poVbjHDkKa+wtC8frBMwQtT3BmqGYBjzMwJ63tuR4=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/digitalocean/godo v1.7.5 h1:JOQbAO6QT1GGjor0doT0mXefX2FgUDPOpYh2RaXA+ko=
Expand Down
2 changes: 1 addition & 1 deletion helper/forwarding/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions helper/identity/mfa/mfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,22 @@ func (c *Config) Clone() (*Config, error) {

return &clonedConfig, nil
}

func (c *MFAEnforcementConfig) Clone() (*MFAEnforcementConfig, error) {
if c == nil {
return nil, fmt.Errorf("nil config")
}

marshaledConfig, err := proto.Marshal(c)
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
}

var clonedConfig MFAEnforcementConfig
err = proto.Unmarshal(marshaledConfig, &clonedConfig)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}

return &clonedConfig, nil
}
327 changes: 244 additions & 83 deletions helper/identity/mfa/types.pb.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions helper/identity/mfa/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ message Config {
DuoConfig duo_config = 8;
PingIDConfig pingid_config = 9;
}
// @inject_tag: sentinel:"-"
string namespace_id = 10;
}

// TOTPConfig represents the configuration information required to generate
Expand Down Expand Up @@ -61,6 +63,8 @@ message DuoConfig {
string api_hostname = 3;
// @inject_tag: sentinel:"-"
string push_info = 4;
// @inject_tag: sentinel:"-"
bool use_passcode = 5;
}

// OktaConfig contains Okta configuration parameters required to perform Okta
Expand Down Expand Up @@ -129,3 +133,16 @@ message TOTPSecret {
// @inject_tag: sentinel:"-"
string key = 9;
}

// MFAEnforcementConfig is what the user provides to the
// mfa/login_enforcement endpoint.
message MFAEnforcementConfig {
string name = 1;
string namespace_id = 2;
repeated string mfa_method_ids = 3;
repeated string auth_method_accessors = 4;
repeated string auth_method_types = 5;
repeated string identity_group_ids = 6;
repeated string identity_entity_ids = 7;
string id = 8;
}
2 changes: 1 addition & 1 deletion helper/identity/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 98 additions & 1 deletion helper/namespace/namespace_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package namespace

import "testing"
import (
"testing"
)

func TestSplitIDFromString(t *testing.T) {
tcases := []struct {
Expand Down Expand Up @@ -72,3 +74,98 @@ func TestSplitIDFromString(t *testing.T) {
}
}
}

func TestHasParent(t *testing.T) {
// Create ns1
ns1 := &Namespace{
ID: "id1",
Path: "ns1/",
}

// Create ns1/ns2
ns2 := &Namespace{
ID: "id2",
Path: "ns1/ns2/",
}

// Create ns1/ns2/ns3
ns3 := &Namespace{
ID: "id3",
Path: "ns1/ns2/ns3/",
}

// Create ns4
ns4 := &Namespace{
ID: "id4",
Path: "ns4/",
}

// Create ns4/ns5
ns5 := &Namespace{
ID: "id5",
Path: "ns4/ns5/",
}

tests := []struct {
name string
parent *Namespace
ns *Namespace
expected bool
}{
{
"is root an ancestor of ns1",
RootNamespace,
ns1,
true,
},
{
"is ns1 an ancestor of ns2",
ns1,
ns2,
true,
},
{
"is ns2 an ancestor of ns3",
ns2,
ns3,
true,
},
{
"is ns1 an ancestor of ns3",
ns1,
ns3,
true,
},
{
"is root an ancestor of ns3",
RootNamespace,
ns3,
true,
},
{
"is ns4 an ancestor of ns3",
ns4,
ns3,
false,
},
{
"is ns5 an ancestor of ns3",
ns5,
ns3,
false,
},
{
"is ns1 an ancestor of ns5",
ns1,
ns5,
false,
},
}

for _, test := range tests {
actual := test.ns.HasParent(test.parent)
if actual != test.expected {
t.Fatalf("bad ancestor calculation; name: %q, actual: %t, expected: %t", test.name, actual, test.expected)
}
}
}
2 changes: 1 addition & 1 deletion helper/storagepacker/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,7 @@ func parseMFAHeader(req *logical.Request) error {

shardSplits := strings.SplitN(mfaHeaderValue, ":", 2)
if shardSplits[0] == "" {
return fmt.Errorf("invalid data in header %q; missing method name", MFAHeaderName)
return fmt.Errorf("invalid data in header %q; missing method name or ID", MFAHeaderName)
}

if shardSplits[1] == "" {
Expand Down
19 changes: 10 additions & 9 deletions http/logical_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,16 @@ func TestLogical_CreateToken(t *testing.T) {
"data": nil,
"wrap_info": nil,
"auth": map[string]interface{}{
"policies": []interface{}{"root"},
"token_policies": []interface{}{"root"},
"metadata": nil,
"lease_duration": json.Number("0"),
"renewable": false,
"entity_id": "",
"token_type": "service",
"orphan": false,
"num_uses": json.Number("0"),
"policies": []interface{}{"root"},
"token_policies": []interface{}{"root"},
"metadata": nil,
"lease_duration": json.Number("0"),
"renewable": false,
"entity_id": "",
"token_type": "service",
"orphan": false,
"mfa_requirement": nil,
"num_uses": json.Number("0"),
},
"warnings": nilWarnings,
}
Expand Down
2 changes: 1 addition & 1 deletion physical/raft/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/database/dbplugin/database.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/database/dbplugin/v5/proto/database.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/helper/pluginutil/multiplexing.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sdk/logical/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ type Auth struct {

// Orphan is set if the token does not have a parent
Orphan bool `json:"orphan"`

// MFARequirement
MFARequirement *MFARequirement `json:"mfa_requirement"`
}

func (a *Auth) GoString() string {
Expand Down
Loading

0 comments on commit 2fe214f

Please sign in to comment.