vault/builtin/credential/github/backend.go
Max Bowsher e47dd9df53
OpenAPI: Separate ListOperation from ReadOperation (#21723)
* OpenAPI: Separate ListOperation from ReadOperation

Historically, since Vault's ReadOperation and ListOperation both map to
the HTTP GET method, their representation in the generated OpenAPI has
been a bit confusing.

This was partially mitigated some time ago, by making the `list` query
parameter express whether it was required or optional - but only in
a way useful to human readers - the human had to know, for example, that
the schema of the response body would change depending on whether `list`
was selected.

Now that there is an effort underway to automatically generate API
clients from the OpenAPI spec, we have a need to fix this more
comprehensively. Fortunately, we do have a means to do so - since Vault
has opinionated treatment of trailing slashes, linked to operations
being list or not, we can use an added trailing slash on the URL path to
separate list operations in the OpenAPI spec.

This PR implements that, and then fixes an operation ID which becomes
duplicated, with this change applied.

See also hashicorp/vault-client-go#174, a bug which will be fixed by
this work.

* Set further DisplayAttrs in auth/github plugin

To mask out more duplicate read/list functionality, now being separately
generated to OpenAPI client libraries as a result of this change.

* Apply requested changes to operation IDs

I'm not totally convinced its worth the extra lines of code, but
equally, I don't have strong feelings about it, so I'll just make the
change.

* Adjust logic to prevent any possibility of generating OpenAPI paths with doubled final slashes

Even in the edge case of improper use of regex patterns and operations.

* changelog

* Fix TestSudoPaths to pass again... which snowballed a bit...

Once I looked hard at it, I found it was missing several sudo paths,
which led to additional bug fixing elsewhere.

I might need to pull some parts of this change out into a separate PR
for ease of review...

* Fix other tests

* More test fixing

* Undo scope creep - back away from fixing sudo paths not shown as such in OpenAPI, at least within this PR

Just add TODO comments for now.
2023-07-13 13:36:52 -04:00

160 lines
4.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package github
import (
"context"
"net/url"
"github.com/google/go-github/github"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"golang.org/x/oauth2"
)
const operationPrefixGithub = "github"
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
func Backend() *backend {
var b backend
b.TeamMap = &framework.PolicyMap{
PathMap: framework.PathMap{
Name: "teams",
},
DefaultKey: "default",
}
teamMapPaths := b.TeamMap.Paths()
teamMapPaths[0].DisplayAttrs = &framework.DisplayAttributes{
OperationPrefix: operationPrefixGithub,
OperationSuffix: "teams",
}
teamMapPaths[1].DisplayAttrs = &framework.DisplayAttributes{
OperationPrefix: operationPrefixGithub,
OperationSuffix: "team-mapping",
}
teamMapPaths[0].Operations = map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: teamMapPaths[0].Callbacks[logical.ListOperation],
Summary: teamMapPaths[0].HelpSynopsis,
},
logical.ReadOperation: &framework.PathOperation{
Callback: teamMapPaths[0].Callbacks[logical.ReadOperation],
Summary: teamMapPaths[0].HelpSynopsis,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "list",
OperationSuffix: "teams2", // The ReadOperation is redundant with the ListOperation
},
},
}
teamMapPaths[0].Callbacks = nil
b.UserMap = &framework.PolicyMap{
PathMap: framework.PathMap{
Name: "users",
},
DefaultKey: "default",
}
userMapPaths := b.UserMap.Paths()
userMapPaths[0].DisplayAttrs = &framework.DisplayAttributes{
OperationPrefix: operationPrefixGithub,
OperationSuffix: "users",
}
userMapPaths[1].DisplayAttrs = &framework.DisplayAttributes{
OperationPrefix: operationPrefixGithub,
OperationSuffix: "user-mapping",
}
userMapPaths[0].Operations = map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: userMapPaths[0].Callbacks[logical.ListOperation],
Summary: userMapPaths[0].HelpSynopsis,
},
logical.ReadOperation: &framework.PathOperation{
Callback: userMapPaths[0].Callbacks[logical.ReadOperation],
Summary: userMapPaths[0].HelpSynopsis,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "list",
OperationSuffix: "users2", // The ReadOperation is redundant with the ListOperation
},
},
}
userMapPaths[0].Callbacks = nil
allPaths := append(teamMapPaths, userMapPaths...)
b.Backend = &framework.Backend{
Help: backendHelp,
PathsSpecial: &logical.Paths{
Unauthenticated: []string{
"login",
},
},
Paths: append([]*framework.Path{pathConfig(&b), pathLogin(&b)}, allPaths...),
AuthRenew: b.pathLoginRenew,
BackendType: logical.TypeCredential,
}
return &b
}
type backend struct {
*framework.Backend
TeamMap *framework.PolicyMap
UserMap *framework.PolicyMap
}
// Client returns the GitHub client to communicate to GitHub via the
// configured settings.
func (b *backend) Client(token string) (*github.Client, error) {
tc := cleanhttp.DefaultClient()
if token != "" {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, tc)
tc = oauth2.NewClient(ctx, &tokenSource{Value: token})
}
client := github.NewClient(tc)
emptyUrl, err := url.Parse("")
if err != nil {
return nil, err
}
client.UploadURL = emptyUrl
return client, nil
}
// tokenSource is an oauth2.TokenSource implementation.
type tokenSource struct {
Value string
}
func (t *tokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{AccessToken: t.Value}, nil
}
const backendHelp = `
The GitHub credential provider allows authentication via GitHub.
Users provide a personal access token to log in, and the credential
provider verifies they're part of the correct organization and then
maps the user to a set of Vault policies according to the teams they're
part of.
After enabling the credential provider, use the "config" route to
configure it.
`