Skip to content

Commit

Permalink
feat: Add (very) basic auth (#462)
Browse files Browse the repository at this point in the history
This commit adds some very basic token-based auth to the flintlock
server.

```
flintlockd run --basic-auth-token foo
```

Note that this is not a bearer token, or a username+password combo. It
is simply a token which will be compared against an **encoded** token on
all server calls.

Clients are responsible for adding an `authorization: basic <encoded
token>` header to all calls.

TLS is next on the list.
  • Loading branch information
Callisto13 authored Jun 21, 2022
1 parent 92eb4c8 commit b4cb1b7
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 23 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (

require (
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/urfave/cli/v2 v2.8.1
github.com/weaveworks-liquidmetal/flintlock/api v0.0.0-20211217111250-5f8d70c4a581
github.com/weaveworks-liquidmetal/flintlock/client v0.0.0-00010101000000-000000000000
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
Expand Down Expand Up @@ -534,6 +536,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
Expand Down Expand Up @@ -711,6 +715,7 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
Expand Down Expand Up @@ -1032,6 +1037,7 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
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=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -1241,6 +1247,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
Expand All @@ -1266,6 +1273,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
Expand Down
34 changes: 28 additions & 6 deletions internal/command/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
kernelSnapshotterFlag = "containerd-kernel-ss"
containerdNamespace = "containerd-ns"
maximumRetryFlag = "maximum-retry"
basicAuthTokenFlag = "basic-auth-token"
)

// AddGRPCServerFlagsToCommand will add gRPC server flags to the supplied command.
Expand All @@ -28,6 +29,21 @@ func AddGRPCServerFlagsToCommand(cmd *cobra.Command, cfg *config.Config) {
grpcEndpointFlag,
defaults.GRPCAPIEndpoint,
"The endpoint for the gRPC server to listen on.")

cmd.Flags().StringVar(&cfg.StateRootDir,
"state-dir",
defaults.StateRootDir,
"The directory to use for the as the root for runtime state.")

cmd.Flags().DurationVar(&cfg.ResyncPeriod,
"resync-period",
defaults.ResyncPeriod,
"Reconcile the specs to resynchronise them based on this period.")

cmd.Flags().DurationVar(&cfg.DeleteVMTimeout,
"deleteMicroVM-timeout",
defaults.DeleteVMTimeout,
"The timeout for deleting a microvm.")
}

// AddGWServerFlagsToCommand will add gRPC HTTP gateway flags to the supplied command.
Expand All @@ -43,6 +59,15 @@ func AddGWServerFlagsToCommand(cmd *cobra.Command, cfg *config.Config) {
"The endpoint for the HTTP proxy to the gRPC service to listen on.")
}

// AddAuthFlagsToCommand will add various auth method flags to the command.
func AddAuthFlagsToCommand(cmd *cobra.Command, cfg *config.Config) {
cmd.Flags().StringVar(&cfg.BasicAuthToken,
basicAuthTokenFlag,
"",
"The token to use for very basic token based authentication.")
}

// AddNetworkFlagsToCommand will add various network flags to the command.
func AddNetworkFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
cmd.Flags().StringVar(&cfg.ParentIface,
parentIfaceFlag,
Expand All @@ -56,6 +81,7 @@ func AddNetworkFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
return nil
}

// AddHiddenFlagsToCommand will add hidden flags to the supplied command.
func AddHiddenFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
cmd.Flags().BoolVar(&cfg.DisableReconcile,
disableReconcileFlag,
Expand Down Expand Up @@ -88,7 +114,7 @@ func AddHiddenFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
}

// AddFirecrackerFlagsToCommand will add the firecracker provider specific flags to the supplied cobra command.
func AddFirecrackerFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
func AddFirecrackerFlagsToCommand(cmd *cobra.Command, cfg *config.Config) {
cmd.Flags().StringVar(&cfg.FirecrackerBin,
firecrackerBinFlag,
defaults.FirecrackerBin,
Expand All @@ -97,12 +123,10 @@ func AddFirecrackerFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error
firecrackerDetachFlag,
defaults.FirecrackerDetach,
"If true the child firecracker processes will be detached from the parent flintlock process.")

return nil
}

// AddContainerDFlagsToCommand will add the containerd specific flags to the supplied cobra command.
func AddContainerDFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
func AddContainerDFlagsToCommand(cmd *cobra.Command, cfg *config.Config) {
cmd.Flags().StringVar(&cfg.CtrSocketPath,
containerdSocketFlag,
defaults.ContainerdSocket,
Expand All @@ -117,6 +141,4 @@ func AddContainerDFlagsToCommand(cmd *cobra.Command, cfg *config.Config) error {
containerdNamespace,
defaults.ContainerdNamespace,
"The name of the containerd namespace to use.")

return nil
}
50 changes: 33 additions & 17 deletions internal/command/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"os/signal"
"sync"

grpc_mw "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
Expand All @@ -17,7 +19,7 @@ import (
"github.com/weaveworks-liquidmetal/flintlock/internal/config"
"github.com/weaveworks-liquidmetal/flintlock/internal/inject"
"github.com/weaveworks-liquidmetal/flintlock/internal/version"
"github.com/weaveworks-liquidmetal/flintlock/pkg/defaults"
"github.com/weaveworks-liquidmetal/flintlock/pkg/auth"
"github.com/weaveworks-liquidmetal/flintlock/pkg/flags"
"github.com/weaveworks-liquidmetal/flintlock/pkg/log"
"google.golang.org/grpc"
Expand Down Expand Up @@ -48,14 +50,9 @@ func NewCommand(cfg *config.Config) (*cobra.Command, error) {
}

cmdflags.AddGRPCServerFlagsToCommand(cmd, cfg)

if err := cmdflags.AddContainerDFlagsToCommand(cmd, cfg); err != nil {
return nil, fmt.Errorf("adding containerd flags to run command: %w", err)
}

if err := cmdflags.AddFirecrackerFlagsToCommand(cmd, cfg); err != nil {
return nil, fmt.Errorf("adding firecracker flags to run command: %w", err)
}
cmdflags.AddAuthFlagsToCommand(cmd, cfg)
cmdflags.AddContainerDFlagsToCommand(cmd, cfg)
cmdflags.AddFirecrackerFlagsToCommand(cmd, cfg)

if err := cmdflags.AddNetworkFlagsToCommand(cmd, cfg); err != nil {
return nil, fmt.Errorf("adding network flags to run command: %w", err)
Expand All @@ -65,10 +62,6 @@ func NewCommand(cfg *config.Config) (*cobra.Command, error) {
return nil, fmt.Errorf("adding hidden flags to run command: %w", err)
}

cmd.Flags().StringVar(&cfg.StateRootDir, "state-dir", defaults.StateRootDir, "The directory to use for the as the root for runtime state.")
cmd.Flags().DurationVar(&cfg.ResyncPeriod, "resync-period", defaults.ResyncPeriod, "Reconcile the specs to resynchronise them based on this period.")
cmd.Flags().DurationVar(&cfg.DeleteVMTimeout, "deleteMicroVM-timeout", defaults.DeleteVMTimeout, "The timeout for deleting a microvm.")

return cmd, nil
}

Expand Down Expand Up @@ -127,11 +120,8 @@ func serveAPI(ctx context.Context, cfg *config.Config) error {

app := inject.InitializeApp(cfg, ports)
server := inject.InitializeGRPCServer(app)
grpcServer := grpc.NewServer(withOpts(ctx, cfg.BasicAuthToken)...)

grpcServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
)
mvmv1.RegisterMicroVMServer(grpcServer, server)
grpc_prometheus.Register(grpcServer)
http.Handle("/metrics", promhttp.Handler())
Expand Down Expand Up @@ -178,3 +168,29 @@ func runControllers(ctx context.Context, cfg *config.Config) error {

return nil
}

func withOpts(ctx context.Context, authToken string) []grpc.ServerOption {
logger := log.GetLogger(ctx)

if authToken != "" {
logger.Info("basic authentication is enabled")

return []grpc.ServerOption{
grpc.StreamInterceptor(grpc_mw.ChainStreamServer(
grpc_prometheus.StreamServerInterceptor,
grpc_auth.StreamServerInterceptor(auth.BasicAuthFunc(authToken)),
)),
grpc.UnaryInterceptor(grpc_mw.ChainUnaryServer(
grpc_prometheus.UnaryServerInterceptor,
grpc_auth.UnaryServerInterceptor(auth.BasicAuthFunc(authToken)),
)),
}
}

logger.Warn("authentication is DISABLED")

return []grpc.ServerOption{
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
}
}
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ type Config struct {
MaximumRetry int
// DeleteVMTimeout defines the timeout for the delete vm operation.
DeleteVMTimeout time.Duration
// BasicAuthToken is the static token to use for very basic authentication.
BasicAuthToken string
}
60 changes: 60 additions & 0 deletions pkg/auth/basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package auth

import (
"context"
"encoding/base64"
"fmt"
"strings"

grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type (
authenticated bool
authMethod string
)

const (
AuthMethod authMethod = ""
Authenticated authenticated = false

basic = "basic"
)

func BasicAuthFunc(expectedToken string) grpc_auth.AuthFunc {
return func(ctx context.Context) (context.Context, error) {
token, err := grpc_auth.AuthFromMD(ctx, basic)
if err != nil {
return nil, fmt.Errorf("could not extract token from request header: %w", err)
}

if err := validateBasicAuthToken(token, expectedToken); err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
}

ctx = context.WithValue(ctx, Authenticated, true)
ctx = context.WithValue(ctx, AuthMethod, basic)

return ctx, nil
}
}

func validateBasicAuthToken(suppliedToken string, expectedToken string) error {
if expectedToken == "" {
return errExpectedTokenRequired
}

if suppliedToken == "" {
return errEmptyAuthToken
}

data := base64.StdEncoding.EncodeToString([]byte(expectedToken))

if strings.Compare(suppliedToken, string(data)) != 0 {
return errFailedBasicAuth
}

return nil
}
63 changes: 63 additions & 0 deletions pkg/auth/basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package auth_test

import (
"context"
"encoding/base64"
"testing"

. "github.com/onsi/gomega"
"github.com/weaveworks-liquidmetal/flintlock/pkg/auth"
"google.golang.org/grpc/metadata"
)

func TestBasicAuth_ValidToken(t *testing.T) {
g := NewWithT(t)

validToken := "validTokenUnencoded"
validTokenEncoded := base64.StdEncoding.EncodeToString([]byte(validToken))

ctx := newIncomingContext(validTokenEncoded)
authFn := auth.BasicAuthFunc(validToken)

newCtx, err := authFn(ctx)
g.Expect(err).NotTo(HaveOccurred())

g.Expect(newCtx.Value(auth.AuthMethod)).To(Equal("basic"))
g.Expect(newCtx.Value(auth.Authenticated)).To(BeTrue())
}

func TestBasicAuth_InvalidToken(t *testing.T) {
g := NewWithT(t)

validToken := "validTokenUnencoded"
invalidToken := "invalid"
invalidTokenEncoded := base64.StdEncoding.EncodeToString([]byte(invalidToken))

ctx := newIncomingContext(invalidTokenEncoded)
authFn := auth.BasicAuthFunc(validToken)

_, err := authFn(ctx)
g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError(ContainSubstring("invalid auth token")))
}

func TestBasicAuth_NoTokenInHeader(t *testing.T) {
g := NewWithT(t)

validToken := "validTokenUnencoded"

ctx := context.Background()
authFn := auth.BasicAuthFunc(validToken)

_, err := authFn(ctx)
g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError(ContainSubstring("could not extract token from request header")))
}

func newIncomingContext(token string) context.Context {
parent := context.Background()
md := metadata.MD{
"authorization": []string{"Basic " + token},
}
return metadata.NewIncomingContext(parent, md)
}
9 changes: 9 additions & 0 deletions pkg/auth/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package auth

import "errors"

var (
errEmptyAuthToken = errors.New("empty authentication token")
errExpectedTokenRequired = errors.New("expected auth token is required")
errFailedBasicAuth = errors.New("failed basic authentication. Check the token supplied")
)

0 comments on commit b4cb1b7

Please sign in to comment.