Skip to content
Open
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
12 changes: 12 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(yamllint:*)",
"Bash(make lint:*)"
],
"deny": [],
"ask": [],
"defaultMode": "acceptEdits"
}
}
128 changes: 128 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
name: CI

on:
push:
branches: [main, rhobs-obs-api-konflux]
pull_request:
branches: [main, rhobs-obs-api-konflux]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Build
run: |
make build
git diff --exit-code

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y xz-utils unzip shellcheck

- name: Install bingo
run: go install github.com/bwplotka/bingo@latest

- name: Lint
run: make lint --always-make

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Install dependencies
run: |
sudo apt-get update && sudo apt-get -y install xz-utils unzip openssl

- name: Test
run: make test --always-make

test-e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y make

- name: Build e2e test container
run: |
echo "Building e2e test container..."
OCI_BIN=docker make container-test
echo "Available Docker images:"
docker images | grep observatorium || echo "No observatorium images found"
echo "Saving container image to cache..."
docker save quay.io/observatorium/api:local_e2e_test > /tmp/e2e-image.tar

- name: Load e2e test container
run: |
echo "Loading e2e test container..."
docker load < /tmp/e2e-image.tar
echo "Verifying image is loaded:"
docker images | grep observatorium

- name: End-to-end tests
run: |
echo "Running e2e tests..."
rm -rf test/e2e/e2e_*
OCI_BIN=docker CGO_ENABLED=1 GO111MODULE=on go test -timeout=25m -race -short -tags integration ./test/e2e 2>&1 | tee test_output.log
exit_code=${PIPESTATUS[0]}
if [ $exit_code -ne 0 ]; then
echo "Tests failed with exit code: $exit_code"
echo "Last 50 lines of output:"
tail -50 test_output.log
fi
exit $exit_code

generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get -y install unzip

- name: Install bingo
run: go install github.com/bwplotka/bingo@latest

- name: Generate and validate
run: |
make generate validate --always-make
make proto
git diff --exit-code
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ Usage of ./observatorium-api:
The endpoint against which to send read requests for metrics.
-metrics.rules.endpoint string
The endpoint against which to make get requests for listing recording/alerting rules and put requests for creating/updating recording/alerting rules.
-metrics.status.endpoint string
The endpoint against which to make requests for status information about metrics (e.g. '/api/v1/status/tsdb').
-metrics.tenant-header string
The name of the HTTP header containing the tenant ID to forward to the metrics upstreams. (default "THANOS-TENANT")
-metrics.tenant-label string
Expand Down
16 changes: 8 additions & 8 deletions authentication/authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,46 +120,46 @@ func TestTokenExpiredErrorHandling(t *testing.T) {
expiredErr := &oidc.TokenExpiredError{
Expiry: time.Now().Add(-time.Hour), // Expired an hour ago
}

// Test direct error
var tokenExpiredErr *oidc.TokenExpiredError
if !errors.As(expiredErr, &tokenExpiredErr) {
t.Error("errors.As should identify TokenExpiredError")
}

// Test wrapped error
wrappedErr := &wrappedError{
msg: "verification failed",
err: expiredErr,
}

if !errors.As(wrappedErr, &tokenExpiredErr) {
t.Error("errors.As should identify wrapped TokenExpiredError")
}
})

t.Run("Other errors are not identified as TokenExpiredError", func(t *testing.T) {
// Test with a generic error
genericErr := errors.New("generic verification error")

var tokenExpiredErr *oidc.TokenExpiredError
if errors.As(genericErr, &tokenExpiredErr) {
t.Error("errors.As should not identify generic error as TokenExpiredError")
}

// Test with wrapped generic error
wrappedGenericErr := &wrappedError{
msg: "verification failed",
err: genericErr,
}

if errors.As(wrappedGenericErr, &tokenExpiredErr) {
t.Error("errors.As should not identify wrapped generic error as TokenExpiredError")
}
})
}

// Helper type to wrap errors for testing
// Helper type to wrap errors for testing.
type wrappedError struct {
msg string
err error
Expand Down
1 change: 0 additions & 1 deletion authentication/mtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,3 @@ func (a MTLSAuthenticator) GRPCMiddleware() grpc.StreamServerInterceptor {
func (a MTLSAuthenticator) Handler() (string, http.Handler) {
return "", nil
}

34 changes: 17 additions & 17 deletions authentication/mtls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
"testing"

"github.com/go-kit/log"

"github.com/observatorium/api/test/testtls"
)

// Helper function to generate test certificates using the existing testtls package
// Helper function to generate test certificates using the existing testtls package.
func setupTestCertificatesWithFile(t testing.TB) (clientCert tls.Certificate, caPath string, cleanup func()) {
t.Helper()

Expand All @@ -26,10 +27,10 @@ func setupTestCertificatesWithFile(t testing.TB) (clientCert tls.Certificate, ca
// Generate certificates using the testtls package
err = testtls.GenerateCerts(
tmpDir,
"test-api", // API common name
"test-api", // API common name
[]string{"localhost", "127.0.0.1"}, // API SANs
"test-dex", // Dex common name
[]string{"localhost"}, // Dex SANs
"test-dex", // Dex common name
[]string{"localhost"}, // Dex SANs
)
if err != nil {
os.RemoveAll(tmpDir)
Expand Down Expand Up @@ -70,12 +71,12 @@ func TestMTLSAuthenticator_PathBasedAuthentication(t *testing.T) {
defer cleanup()

tests := []struct {
name string
pathPatterns []string
requestPath string
expectMTLS bool
expectError bool
description string
name string
pathPatterns []string
requestPath string
expectMTLS bool
expectError bool
description string
}{
{
name: "no_patterns_enforces_all_paths",
Expand Down Expand Up @@ -139,7 +140,7 @@ func TestMTLSAuthenticator_PathBasedAuthentication(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Create mTLS config with path patterns using file-based CA
config := map[string]interface{}{
"caPath": caPath, // Use file-based CA as original code expects
"caPath": caPath, // Use file-based CA as original code expects
"pathPatterns": tt.pathPatterns,
}

Expand Down Expand Up @@ -319,7 +320,7 @@ func TestMTLSAuthenticator_InvalidPathPattern(t *testing.T) {
}
}

// Test path matching logic without requiring certificate validation
// Test path matching logic without requiring certificate validation.
func TestMTLSAuthenticator_PathMatchingLogic(t *testing.T) {
tests := []struct {
name string
Expand All @@ -338,7 +339,7 @@ func TestMTLSAuthenticator_PathMatchingLogic(t *testing.T) {
{
name: "pattern_matches_requires_mtls",
pathPatterns: []string{"/api/.*/receive"},
requestPath: "/api/metrics/v1/receive",
requestPath: "/api/metrics/v1/receive",
expectSkip: false,
description: "Matching pattern requires mTLS",
},
Expand Down Expand Up @@ -379,7 +380,7 @@ func TestMTLSAuthenticator_PathMatchingLogic(t *testing.T) {
}

middleware := authenticator.Middleware()

handlerCalled := false
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
Expand Down Expand Up @@ -413,7 +414,7 @@ func TestMTLSAuthenticator_PathMatchingLogic(t *testing.T) {
}
}

// Test both CA configuration methods work correctly
// Test both CA configuration methods work correctly.
func TestMTLSAuthenticator_CAConfiguration(t *testing.T) {
// Test file-based CA configuration
t.Run("file_based_ca", func(t *testing.T) {
Expand Down Expand Up @@ -449,7 +450,7 @@ func TestMTLSAuthenticator_CAConfiguration(t *testing.T) {
}

config := map[string]interface{}{
"ca": caPEM, // Direct CA data
"ca": caPEM, // Direct CA data
}

logger := log.NewNopLogger()
Expand All @@ -465,4 +466,3 @@ func TestMTLSAuthenticator_CAConfiguration(t *testing.T) {
}
})
}

12 changes: 6 additions & 6 deletions authentication/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ func init() {

// oidcConfig represents the oidc authenticator config.
type oidcConfig struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
GroupClaim string `json:"groupClaim"`
IssuerRawCA []byte `json:"issuerCA"`
IssuerCAPath string `json:"issuerCAPath"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
GroupClaim string `json:"groupClaim"`
IssuerRawCA []byte `json:"issuerCA"`
IssuerCAPath string `json:"issuerCAPath"`
issuerCA *x509.Certificate
IssuerURL string `json:"issuerURL"`
RedirectURL string `json:"redirectURL"`
Expand Down Expand Up @@ -299,7 +299,7 @@ func (a oidcAuthenticator) Middleware() Middleware {
break
}
}

// If path doesn't match, skip OIDC enforcement
if !pathMatches {
next.ServeHTTP(w, r)
Expand Down
Loading
Loading