Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Add ability to embed and use OAuth creds in a waypoint token #3298

Merged
merged 5 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/3298.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
core: Add ability to have cli and runners use OAuth2 to get an auth token
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ require (
go.opencensus.io v0.23.0
go.uber.org/goleak v1.1.10
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
google.golang.org/api v0.44.0
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2148,8 +2148,9 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
6 changes: 5 additions & 1 deletion internal/server/httpapi/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
"github.com/hashicorp/waypoint/pkg/server/gen/mocks"
)

// This code uses the magic token value 445DHu. This value is a base58 encoded
// empty token. An empty token has the magic 'wp24' at the beginning, so this
// empty token is just base58.Encode([]byte("wp24"))

func TestHandleExec(t *testing.T) {
ctx := context.Background()
require := require.New(t)
Expand All @@ -31,7 +35,7 @@ func TestHandleExec(t *testing.T) {
defer httpServer.Close()

// Dial it up
conn, _, err := websocket.Dial(ctx, httpServer.URL+"?token=foo-bar-baz", nil)
conn, _, err := websocket.Dial(ctx, httpServer.URL+"?token=445DHu", nil)
require.NoError(err)
defer conn.Close(websocket.StatusInternalError, "early exit")

Expand Down
8 changes: 5 additions & 3 deletions internal/server/httpapi/trigger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
pb "github.com/hashicorp/waypoint/pkg/server/gen"
)

// For a note about the magic value 445DHu, see exec_test.go

func TestHandleTrigger(t *testing.T) {
require := require.New(t)

Expand All @@ -27,7 +29,7 @@ func TestHandleTrigger(t *testing.T) {
defer httpServer.Close()

// Mock a request
resp, err := http.Get(httpServer.URL + "/v1/trigger/123" + "?token=foo-bar-baz&stream=true")
resp, err := http.Get(httpServer.URL + "/v1/trigger/123" + "?token=445DHu&stream=true")
if err != nil {
t.Errorf("failed to make http request: %s", err)
}
Expand Down Expand Up @@ -78,7 +80,7 @@ func TestHandleTrigger_BadFailures(t *testing.T) {
defer httpServer.Close()

// Mock a request
resp, err := http.Get(httpServer.URL + "/v1/trigger/123" + "?token=foo-bar-baz&stream=true")
resp, err := http.Get(httpServer.URL + "/v1/trigger/123" + "?token=445DHu&stream=true")
if err != nil {
t.Errorf("failed to make http request: %s", err)
}
Expand Down Expand Up @@ -120,7 +122,7 @@ func TestHandleTrigger_CancelStream(t *testing.T) {
defer httpServer.Close()

// Mock a request
req, err := http.NewRequest("GET", httpServer.URL+"/v1/trigger/123"+"?token=foo-bar-baz&stream=true", nil)
req, err := http.NewRequest("GET", httpServer.URL+"/v1/trigger/123"+"?token=445DHu&stream=true", nil)
if err != nil {
t.Errorf("failed to make http request: %s", err)
}
Expand Down
102 changes: 97 additions & 5 deletions internal/serverclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,33 @@ package serverclient
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/url"
"os"
"time"

"github.com/pkg/errors"

"github.com/hashicorp/go-hclog"
"golang.org/x/oauth2/clientcredentials"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/oauth"
"google.golang.org/grpc/keepalive"

"github.com/hashicorp/waypoint/internal/clicontext"
"github.com/hashicorp/waypoint/internal/env"
"github.com/hashicorp/waypoint/pkg/protocolversion"
pb "github.com/hashicorp/waypoint/pkg/server/gen"
"github.com/hashicorp/waypoint/pkg/serverconfig"
)

// ErrNoServerConfig is the error when there is no server configuration
// found for connection.
var ErrNoServerConfig = errors.New("no server connection configuration found")
var (
ErrNoServerConfig = errors.New("no server connection configuration found")
ErrInvalidToken = errors.New("invalid token detected")
)

// ConnectOption is used to configure how Waypoint server connection
// configuration is sourced.
Expand Down Expand Up @@ -102,15 +110,99 @@ func Connect(ctx context.Context, opts ...ConnectOption) (*grpc.ClientConn, erro
token = ""
}
}
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(ContextToken(token)))

cfg.Log.Debug("connection information",
var perRPC credentials.PerRPCCredentials = ContextToken(token)

logArgs := []interface{}{
"address", cfg.Addr,
"tls", cfg.Tls,
"tls_skip_verify", cfg.TlsSkipVerify,
"send_auth", cfg.Auth,
"has_token", token != "",
)
}

/*

A wonderful flow diagram of what is happening when the Waypoint token contains
OAuth2 creds:

(Not in the PR)
┌───────────8. OAuth dance to ─────────────────┐
│ validate token. │
│ ▼
│ ┌──────────┐
│ │ OAuth │
│ ┌──│ Server │◀─┐
│ │ │ │ │
│ │ └──────────┘ │
│ │ │
│ │ │
│ 6. Yeah ok, I │
│ know you. 5. Can I have
│ ┌─────────────────────┐ │ an OAuth
│ │ │ │ token pls?
▼ ▼ │ └────────┐ Here's my
┌────────────┐ 7. Hi! Can │ info.
│ Waypoint │ you handle │ │
┌─▶│ Server │──────┐ │this │ │
│ │ │ │ request, │ │
│ └────────────┘ │ here's my ▼ │
│ │ OAuth Λ │
│ 2. Here you token. ╱ ╲ │
│ go, I │ ╱ ╲ │
│ smuggled an │ ╱ ╲ │
│ OAuth │ ╱ ╲ │
1. I want server └───────────────▕ client ▏──┘
a Waypoint address & ╲ ╱
token credentials ╲ 4. Oh, there's
│ in it. ╲ OAuth info in
│ │ ╲ ╱ this!
│ │ V
│ │ ▲
│ │ │
│ .─────. │ 3. Client, do
│ ; User : │ your thing.
└───────────: ;◀─┘ Here's my
╲ ╱ Waypoint
`───' access token
│ │
│ │
└────────────────────────────────────┘
*/

if token != "" {
tokenData, err := TokenDecode(token)
if err != nil {
return nil, err
}

if tokenData.ExternalCreds != nil {
if oc, ok := tokenData.ExternalCreds.(*pb.Token_OauthCreds); ok {
conf := &clientcredentials.Config{
ClientID: oc.OauthCreds.ClientId,
ClientSecret: oc.OauthCreds.ClientSecret,
TokenURL: oc.OauthCreds.Url,
EndpointParams: url.Values{"audience": {"waypoint-cli"}},
}

oauthToken, err := conf.Token(ctx)
if err != nil {
return nil, err
}

perRPC = oauth.NewOauthAccess(oauthToken)

logArgs = append(logArgs,
"oauth-url", oc.OauthCreds.Url,
"oauth-client-id", oc.OauthCreds.ClientId,
)
}
}
}

grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(perRPC))

cfg.Log.Debug("connection information", logArgs...)

// Connect to this server
return grpc.DialContext(ctx, cfg.Addr, grpcOpts...)
Expand Down
Loading