Skip to content

Commit

Permalink
Vault 5917 allow patch operations to pki roles issuers (#15510)
Browse files Browse the repository at this point in the history
* Add a warning when Issuing Certificate set on a role does not resolve.

* Ivanka's requests - add a warning on deleting issuer or changing it's name.

* Fix nil checks; reduce number of roles to iterate through; only verify roles after migration.

* Fix semgrep failure, ignore roles deleted behind our back.

* Patch functionality for roles

* Make Patch Roles work again, add back patch issuers.

* Add changelog.

* Fix nil-reversion on empty response.

* Panics are bad. don't do that.
  • Loading branch information
kitography authored May 20, 2022
1 parent 065d534 commit bdd7d7d
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 16 deletions.
156 changes: 156 additions & 0 deletions builtin/logical/pki/path_fetch_issuers.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ and always set.`,
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
logical.PatchOperation: &framework.PathOperation{
Callback: b.pathPatchIssuer,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},

HelpSynopsis: pathGetIssuerHelpSyn,
Expand Down Expand Up @@ -319,6 +325,156 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
return response, err
}

func (b *backend) pathPatchIssuer(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Since we're planning on updating issuers here, grab the lock so we've
// got a consistent view.
b.issuersLock.Lock()
defer b.issuersLock.Unlock()

if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not patch issuer until migration has completed"), nil
}

// First we fetch the issuer
issuerName := getIssuerRef(data)
if len(issuerName) == 0 {
return logical.ErrorResponse("missing issuer reference"), nil
}

ref, err := resolveIssuerReference(ctx, req.Storage, issuerName)
if err != nil {
return nil, err
}
if ref == "" {
return logical.ErrorResponse("unable to resolve issuer id for reference: " + issuerName), nil
}

issuer, err := fetchIssuerById(ctx, req.Storage, ref)
if err != nil {
return nil, err
}

// Now We are Looking at What (Might) Have Changed
modified := false

// Name Changes First
_, ok := data.GetOk("issuer_name") // Don't check for conflicts if we aren't updating the name
var oldName string
var newName string
if ok {
newName, err = getIssuerName(ctx, req.Storage, data)
if err != nil && err != errIssuerNameInUse {
// If the error is name already in use, and the new name is the
// old name for this issuer, we're not actually updating the
// issuer name (or causing a conflict) -- so don't err out. Other
// errs should still be surfaced, however.
return logical.ErrorResponse(err.Error()), nil
}
if err == errIssuerNameInUse && issuer.Name != newName {
// When the new name is in use but isn't this name, throw an error.
return logical.ErrorResponse(err.Error()), nil
}
if newName != issuer.Name {
oldName = issuer.Name
issuer.Name = newName
modified = true
}
}

// Leaf Not After Changes
rawLeafBehaviorData, ok := data.GetOk("leaf_not_after_behaivor")
if ok {
rawLeafBehavior := rawLeafBehaviorData.(string)
var newLeafBehavior certutil.NotAfterBehavior
switch rawLeafBehavior {
case "err":
newLeafBehavior = certutil.ErrNotAfterBehavior
case "truncate":
newLeafBehavior = certutil.TruncateNotAfterBehavior
case "permit":
newLeafBehavior = certutil.PermitNotAfterBehavior
default:
return logical.ErrorResponse("Unknown value for field `leaf_not_after_behavior`. Possible values are `err`, `truncate`, and `permit`."), nil
}
if newLeafBehavior != issuer.LeafNotAfterBehavior {
issuer.LeafNotAfterBehavior = newLeafBehavior
modified = true
}
}

// Usage Changes
rawUsageData, ok := data.GetOk("usage")
if ok {
rawUsage := rawUsageData.([]string)
newUsage, err := NewIssuerUsageFromNames(rawUsage)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Unable to parse specified usages: %v - valid values are %v", rawUsage, AllIssuerUsages.Names())), nil
}
if newUsage != issuer.Usage {
issuer.Usage = newUsage
modified = true
}
}

// Manual Chain Changes
newPathData, ok := data.GetOk("manual_chain")
if ok {
newPath := newPathData.([]string)
var updateChain bool
var constructedChain []issuerID
for index, newPathRef := range newPath {
// Allow self for the first entry.
if index == 0 && newPathRef == "self" {
newPathRef = string(ref)
}

resolvedId, err := resolveIssuerReference(ctx, req.Storage, newPathRef)
if err != nil {
return nil, err
}

if index == 0 && resolvedId != ref {
return logical.ErrorResponse(fmt.Sprintf("expected first cert in chain to be a self-reference, but was: %v/%v", newPathRef, resolvedId)), nil
}

constructedChain = append(constructedChain, resolvedId)
if len(issuer.ManualChain) < len(constructedChain) || constructedChain[index] != issuer.ManualChain[index] {
updateChain = true
}
}

if len(issuer.ManualChain) != len(constructedChain) {
updateChain = true
}

if updateChain {
issuer.ManualChain = constructedChain

// Building the chain will write the issuer to disk; no need to do it
// twice.
modified = false
err := rebuildIssuersChains(ctx, req.Storage, issuer)
if err != nil {
return nil, err
}
}
}

if modified {
err := writeIssuer(ctx, req.Storage, issuer)
if err != nil {
return nil, err
}
}

response, err := respondReadIssuer(issuer)
if newName != oldName {
addWarningOnDereferencing(oldName, response, ctx, req.Storage)
}

return response, err
}

func (b *backend) pathGetRawIssuer(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not get issuer until migration has completed"), nil
Expand Down
Loading

0 comments on commit bdd7d7d

Please sign in to comment.