Skip to content
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

fix: dont require auth for healthchecks #2350

Merged
merged 7 commits into from
Nov 6, 2023
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v1.30.1](https://github.com/flipt-io/flipt/releases/tag/v1.30.1) - 2023-11-06

### Fixed

- Exclude health check from auth (#2350)

## [v1.30.0](https://github.com/flipt-io/flipt/releases/tag/v1.30.0) - 2023-10-31

### Added
Expand Down
24 changes: 23 additions & 1 deletion build/testing/integration/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"testing"

"github.com/gofrs/uuid"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.flipt.io/flipt/build/testing/integration"
"go.flipt.io/flipt/rpc/flipt"
"go.flipt.io/flipt/rpc/flipt/evaluation"
sdk "go.flipt.io/flipt/sdk/go"
"google.golang.org/protobuf/testing/protocmp"
)

func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, authenticated bool) {
func API(t *testing.T, ctx context.Context, client sdk.SDK, opts integration.TestOpts) {
var (
namespace = opts.Namespace
authenticated = opts.Authenticated
addr = opts.Addr
protocol = opts.Protocol
)

t.Run("Namespaces", func(t *testing.T) {
if !namespaceIsDefault(namespace) {
t.Log(`Create namespace.`)
Expand Down Expand Up @@ -1375,6 +1384,19 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
require.NoError(t, err)
})
})

t.Run("Healthcheck", func(t *testing.T) {
if protocol == "grpc" {
t.Skip("TODO: we do not support healthcheck test for grpc yet")
}
t.Run("HTTP", func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf("%s/health", addr))
require.NoError(t, err)

assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
assert.Equal(t, http.StatusOK, resp.StatusCode)
})
})
}

func namespaceIsDefault(ns string) bool {
Expand Down
6 changes: 3 additions & 3 deletions build/testing/integration/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
)

func TestAPI(t *testing.T) {
integration.Harness(t, func(t *testing.T, sdk sdk.SDK, namespace string, authentication bool) {
integration.Harness(t, func(t *testing.T, sdk sdk.SDK, opts integration.TestOpts) {
ctx := context.Background()

api.API(t, ctx, sdk, namespace, authentication)
api.API(t, ctx, sdk, opts)

// run extra tests in authenticated context
if authentication {
if opts.Authenticated {
api.Authenticated(t, sdk)
}
})
Expand Down
16 changes: 14 additions & 2 deletions build/testing/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ var (
fliptNamespace = flag.String("flipt-namespace", "", "Namespace used to scope API calls.")
)

func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, ns string, authenticated bool)) {
type TestOpts struct {
Addr string
Protocol string
Namespace string
Authenticated bool
}

func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, opts TestOpts)) {
var transport sdk.Transport

protocol, host, _ := strings.Cut(*fliptAddr, "://")
Expand Down Expand Up @@ -54,6 +61,11 @@ func Harness(t *testing.T, fn func(t *testing.T, sdk sdk.SDK, ns string, authent

name := fmt.Sprintf("[Protocol %q; Namespace %q; Authentication %v]", protocol, namespace, authentication)
t.Run(name, func(t *testing.T) {
fn(t, sdk.New(transport, opts...), namespace, authentication)
fn(t, sdk.New(transport, opts...), TestOpts{
Protocol: protocol,
Addr: *fliptAddr,
Namespace: namespace,
Authenticated: authentication},
)
})
}
9 changes: 7 additions & 2 deletions build/testing/integration/readonly/readonly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ import (
// folder has been loaded into the target instance being tested.
// It then exercises a bunch of read operations via the provided SDK in the target namespace.
func TestReadOnly(t *testing.T) {
integration.Harness(t, func(t *testing.T, sdk sdk.SDK, namespace string, authenticated bool) {
ctx := context.Background()
integration.Harness(t, func(t *testing.T, sdk sdk.SDK, opts integration.TestOpts) {
var (
ctx = context.Background()
namespace = opts.Namespace
authenticated = opts.Authenticated
)

ns, err := sdk.Flipt().GetNamespace(ctx, &flipt.GetNamespaceRequest{
Key: namespace,
})
Expand Down
1 change: 1 addition & 0 deletions examples/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ For more information on how to secure your Flipt instance and setup authenticati

* [Reverse Proxy Authentication](proxy/README.md)
* [OIDC Authentication with Dex](dex/README.md)
* [Static Token Authentication](token/README.md)
6 changes: 3 additions & 3 deletions examples/authentication/dex/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ version: "3"
services:
dex:
image: dexidp/dex:latest
command: dex serve /etc/dex/config.yaml
command: dex serve /etc/dex/config.yml
ports:
- "5556:5556"
volumes:
- ./dex-config.yaml:/etc/dex/config.yaml
- ./dex-config.yml:/etc/dex/config.yml
networks:
- flipt_network

Expand All @@ -17,7 +17,7 @@ services:
ports:
- "8080:8080"
volumes:
- ./config.yaml:/etc/flipt/config/default.yml
- ./config.yml:/etc/flipt/config/default.yml
environment:
- FLIPT_LOG_LEVEL=debug
- FLIPT_META_TELEMETRY_ENABLED=false
Expand Down
51 changes: 51 additions & 0 deletions examples/authentication/token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Static Token Authentication Example

This example shows how you can secure your Flipt instance with a static bootstrap token: <https://www.flipt.io/docs/configuration/authentication#method-static-token>

**Note:** You will not be able to use the Flipt UI with this example as it does not support static token authentication.
See the [Dex example](../dex/README.md) to see how to configure Flipt to use OIDC for authentication and enable the UI.

## Requirements

To run this example application you'll need:

* [Docker](https://docs.docker.com/install/)
* [docker-compose](https://docs.docker.com/compose/install/)

## Running the Example

1. Run `docker-compose up` from this directory
1. Try to get a list of flags without authenticating using the REST API:

```shell
❯ curl -v http://localhost:8080/api/v1/flags

> GET /api/v1/flags HTTP/1.1
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Content-Type: text/plain; charset=utf-8
```

1. You should get a **401 Unauthorized** response as no authentication was present on the request
1. Try again, providing the bootstrap token `secret`, specified in the [docker-compose.yml](docker-compose.yml) file:

```shell
~ » curl -v -H 'Authorization: Bearer secret' http://localhost:8080/api/v1/flags

> GET /api/v1/flags HTTP/1.1
> Host: localhost:8080
> Accept: */*
> Authorization: Bearer secret
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Grpc-Metadata-Content-Type: application/grpc
< X-Content-Type-Options: nosniff
< Content-Length: 46
<
{"flags":[],"nextPageToken":"","totalCount":0}
```

1. This time the request succeeds and a **200 OK** response is returned
17 changes: 17 additions & 0 deletions examples/authentication/token/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
log:
level: DEBUG

db:
url: file:/var/opt/flipt/flipt.db

authentication:
required: true
methods:
token:
enabled: true
bootstrap:
token: "secret"
expiration: 24h
cleanup:
interval: 2h
grace_period: 48h
18 changes: 18 additions & 0 deletions examples/authentication/token/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3"

services:
flipt:
image: flipt/flipt:latest
command: ["./flipt", "--force-migrate"]
ports:
- "8080:8080"
volumes:
- ./config.yml:/etc/flipt/config/default.yml
environment:
- FLIPT_LOG_LEVEL=debug
- FLIPT_META_TELEMETRY_ENABLED=false
networks:
- flipt_network

networks:
flipt_network:
22 changes: 14 additions & 8 deletions internal/cmd/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,18 @@ func NewGRPCServer(
}

var (
fliptsrv = fliptserver.New(logger, store)
metasrv = metadata.New(cfg, info)
evalsrv = evaluation.New(logger, store)
authOpts = []containers.Option[auth.InterceptorOptions]{}
fliptsrv = fliptserver.New(logger, store)
metasrv = metadata.New(cfg, info)
evalsrv = evaluation.New(logger, store)
healthsrv = health.NewServer()
)

var (
// authOpts is a slice of options that will be passed to the authentication service.
// it's initialized with the default option of skipping authentication for the health service which should never require authentication.
authOpts = []containers.Option[auth.InterceptorOptions]{
auth.WithServerSkipsAuthentication(healthsrv),
}
skipAuthIfExcluded = func(server any, excluded bool) {
if excluded {
authOpts = append(authOpts, auth.WithServerSkipsAuthentication(server))
Expand Down Expand Up @@ -433,13 +441,11 @@ func NewGRPCServer(

// initialize grpc server
server.Server = grpc.NewServer(grpcOpts...)

healthserver := health.NewServer()
grpc_health.RegisterHealthServer(server.Server, healthserver)
grpc_health.RegisterHealthServer(server.Server, healthsrv)

// register grpcServer graceful stop on shutdown
server.onShutdown(func(context.Context) error {
healthserver.Shutdown()
healthsrv.Shutdown()
server.GracefulStop()
return nil
})
Expand Down