-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restrict cert auth by CIDR #4478
Changes from 4 commits
a37c56e
cbe4684
715e369
6c19773
c5b9f29
0bc56b2
a5a14c6
d33ca9e
f207565
ea75737
cbb5cad
c9b4321
25d5f6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
"strings" | ||
"time" | ||
|
||
"github.com/hashicorp/go-sockaddr" | ||
"github.com/hashicorp/vault/helper/policyutil" | ||
"github.com/hashicorp/vault/logical" | ||
"github.com/hashicorp/vault/logical/framework" | ||
|
@@ -88,6 +89,11 @@ should never expire. The token should be renewed within the | |
duration specified by this value. At each renewal, the token's | ||
TTL will be set to the value of this parameter.`, | ||
}, | ||
"bound_cidrs": &framework.FieldSchema{ | ||
Type: framework.TypeCommaStringSlice, | ||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of | ||
IP addresses which can perform the login operation.`, | ||
}, | ||
}, | ||
|
||
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
|
@@ -228,6 +234,23 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr | |
} | ||
} | ||
|
||
var parsedCIDRs []*sockaddr.SockAddrMarshaler | ||
if boundCIDRListRaw, ok := d.GetOk("bound_cidrs"); ok { | ||
|
||
var boundCIDRList []string | ||
if boundCIDRs, ok := boundCIDRListRaw.([]string); ok { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a zero value for TypeCommaStringSlice of nil slice and this cast checking is not required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🌮 |
||
boundCIDRList = boundCIDRs | ||
} | ||
|
||
for _, v := range boundCIDRList { | ||
parsedCIDR, err := sockaddr.NewSockAddr(v) | ||
if err != nil { | ||
return nil, err | ||
} | ||
parsedCIDRs = append(parsedCIDRs, &sockaddr.SockAddrMarshaler{parsedCIDR}) | ||
} | ||
} | ||
|
||
certEntry := &CertEntry{ | ||
Name: name, | ||
Certificate: certificate, | ||
|
@@ -238,6 +261,7 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr | |
TTL: ttl, | ||
MaxTTL: maxTTL, | ||
Period: period, | ||
BoundCIDRs: parsedCIDRs, | ||
} | ||
|
||
// Store it | ||
|
@@ -266,6 +290,7 @@ type CertEntry struct { | |
Period time.Duration | ||
AllowedNames []string | ||
RequiredExtensions []string | ||
BoundCIDRs []*sockaddr.SockAddrMarshaler | ||
} | ||
|
||
const pathCertHelpSyn = ` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ import ( | |
"github.com/hashicorp/vault/logical" | ||
"github.com/hashicorp/vault/logical/framework" | ||
|
||
"github.com/hashicorp/go-sockaddr" | ||
"github.com/ryanuber/go-glob" | ||
) | ||
|
||
|
@@ -71,6 +72,10 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra | |
return nil, nil | ||
} | ||
|
||
if err := b.checkCIDR(matched.Entry, req); err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the error a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, it will already be one because that's what the |
||
} | ||
|
||
clientCerts := req.Connection.ConnState.PeerCertificates | ||
if len(clientCerts) == 0 { | ||
return logical.ErrorResponse("no client certificate found"), nil | ||
|
@@ -101,6 +106,7 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *fra | |
Alias: &logical.Alias{ | ||
Name: clientCerts[0].SerialNumber.String(), | ||
}, | ||
BoundCIDRs: matched.Entry.BoundCIDRs, | ||
}, | ||
} | ||
|
||
|
@@ -152,6 +158,10 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f | |
return nil, nil | ||
} | ||
|
||
if err := b.checkCIDR(cert, req); err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That also will already be that error. |
||
} | ||
|
||
if !policyutil.EquivalentPolicies(cert.Policies, req.Auth.Policies) { | ||
return nil, fmt.Errorf("policies have changed, not renewing") | ||
} | ||
|
@@ -160,6 +170,7 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f | |
resp.Auth.TTL = cert.TTL | ||
resp.Auth.MaxTTL = cert.MaxTTL | ||
resp.Auth.Period = cert.Period | ||
resp.Auth.BoundCIDRs = cert.BoundCIDRs | ||
return resp, nil | ||
} | ||
|
||
|
@@ -371,6 +382,33 @@ func (b *backend) checkForValidChain(chains [][]*x509.Certificate) bool { | |
return false | ||
} | ||
|
||
func (b *backend) checkCIDR(cert *CertEntry, req *logical.Request) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering that this function can be potentially used by all the backends that introduces There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I can do that. I was thinking I'd do it in the next iteration because it's not yet reused in this PR, but I can just do it now too. |
||
|
||
if len(cert.BoundCIDRs) <= 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Len is never going to be below zero. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine! A previous dev team I was on preferred it that way because it was "more defensive". Will update. |
||
// short-circuit the below logic | ||
return nil | ||
} | ||
|
||
var valid bool | ||
remoteSockAddr, err := sockaddr.NewSockAddr(req.Connection.RemoteAddr) | ||
if err != nil { | ||
if b.Logger().IsDebug() { | ||
b.Logger().Debug("could not parse remote addr into sockaddr", "error", err, "remote_addr", req.Connection.RemoteAddr) | ||
} | ||
return logical.ErrPermissionDenied | ||
} | ||
for _, cidr := range cert.BoundCIDRs { | ||
if cidr.Contains(remoteSockAddr) { | ||
valid = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than setting the bool, you could just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
break | ||
} | ||
} | ||
if !valid { | ||
return logical.ErrPermissionDenied | ||
} | ||
return nil | ||
} | ||
|
||
// parsePEM parses a PEM encoded x509 certificate | ||
func parsePEM(raw []byte) (certs []*x509.Certificate) { | ||
for len(raw) > 0 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ package logical | |
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/hashicorp/go-sockaddr" | ||
) | ||
|
||
// Auth is the resulting authentication information that is part of | ||
|
@@ -69,6 +71,9 @@ type Auth struct { | |
// mappings groups for the group aliases in identity store. For all the | ||
// matching groups, the entity ID of the user will be added. | ||
GroupAliases []*Alias `json:"group_aliases" mapstructure:"group_aliases" structs:"group_aliases"` | ||
|
||
// The set of CIDRs that this token can be used with | ||
BoundCIDRs []*sockaddr.SockAddrMarshaler `json:"bound_cidrs"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will also need to be added to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for point that out! I will tackle this after #4501 is merged. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't forget about this :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh nm, you already did it. |
||
} | ||
|
||
func (a *Auth) GoString() string { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern is usually only required for paths that handle both create and update operations (have an existence check). I think you could simplify this by just iterating of the the list without checking if the parameter is set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍