-
Notifications
You must be signed in to change notification settings - Fork 0
/
authz.go
156 lines (133 loc) · 4.05 KB
/
authz.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Package clauthz provides Authorization (AuthZ) functionality.
package clauthz
import (
"bytes"
"context"
"fmt"
"io/fs"
"strings"
"github.com/crewlinker/clgo/clconfig"
"github.com/open-policy-agent/opa/logging"
"github.com/open-policy-agent/opa/sdk"
"go.uber.org/fx"
"go.uber.org/zap"
"go.uber.org/zap/zapio"
_ "embed"
)
// Config configures the package.
type Config struct {
// id for the system that is unning OPA.
OPASystemID string `env:"OPA_SYSTEM_ID" envDefault:"auth"`
}
//go:embed opa.yml
var cfg []byte
// Authz provides authn and authz functionality. It includes a simple web server that
// serves our policy bundle on a random port on localhost.
type Authz struct {
cfg Config
bsrv BundleServer
logs *zap.Logger
opa *sdk.OPA
opaw *zapio.Writer
}
// NewAuthz inits the auth service.
func NewAuthz(cfg Config, logs *zap.Logger, bsrv BundleServer) (a *Authz, err error) {
return &Authz{
bsrv: bsrv,
cfg: cfg,
logs: logs.Named("authz"),
opaw: &zapio.Writer{Log: logs.Named("opa")},
}, nil
}
// Start the auth service.
func (a *Authz) Start(ctx context.Context) (err error) {
// pass ologs to zap
ologs := logging.New()
ologs.SetOutput(a.opaw)
// setup OPA with a config with the service_url replaced
a.opa, err = sdk.New(ctx, sdk.Options{
ID: a.cfg.OPASystemID,
Config: bytes.NewReader(bytes.ReplaceAll(cfg, []byte(`$SERVICE_URL$`), []byte(a.bsrv.URL()))),
Logger: ologs,
ConsoleLogger: ologs,
})
if err != nil {
return fmt.Errorf("failed to init opa: %w", err)
}
return nil
}
// Stop the auth service.
func (a *Authz) Stop(ctx context.Context) (err error) {
if err := a.opaw.Close(); err != nil {
return fmt.Errorf("failed to close zap writer: %w", err)
}
return
}
// IsAuthorized the user for a given setup.
func (a *Authz) IsAuthorized(ctx context.Context, inp any) (bool, error) {
res, err := a.opa.Decision(ctx, sdk.DecisionOptions{
Path: "/authz/allow",
Input: inp,
})
if err != nil {
return false, fmt.Errorf("failed to decide: %w", err)
}
allow, ok := res.Result.(bool)
if !ok {
return false, fmt.Errorf("decision did not return bool, but: %T", res.Result) //nolint:goerr113
}
return allow, nil
}
// moduleName for consistent component naming.
const moduleName = "clauthz"
// AllowAll policy always returns allow, for testing.
func AllowAll() map[string]string {
return map[string]string{
"main.rego": `
package authz
import rego.v1
default allow := true
`,
}
}
// Provide the auth components as an fx dependency.
func Provide() fx.Option {
return fx.Module(moduleName,
// provide the environment configuration
clconfig.Provide[Config](strings.ToUpper(moduleName)+"_"),
// the incoming logger will be named after the module
fx.Decorate(func(l *zap.Logger) *zap.Logger { return l.Named(moduleName) }),
// provide the webos webhooks client
fx.Provide(fx.Annotate(NewAuthz,
fx.OnStart(func(ctx context.Context, a *Authz) error { return a.Start(ctx) }),
fx.OnStop(func(ctx context.Context, a *Authz) error { return a.Stop(ctx) }),
)),
)
}
// BundleProvide provides a bundle server.
func BundleProvide(bfs fs.FS) fx.Option {
return fx.Options(
fx.Supply(BundleFS{FS: bfs}),
fx.Provide(fx.Annotate(NewFSBundles,
fx.OnStart(func(ctx context.Context, a *FSBundles) error { return a.Start(ctx) }),
fx.OnStop(func(ctx context.Context, a *FSBundles) error { return a.Stop(ctx) }),
)),
fx.Provide(func(bs *FSBundles) BundleServer { return bs }),
)
}
// TestProvide provides authn authz dependencies that are easy to use in
// tests.
func TestProvide(policies map[string]string) fx.Option {
return fx.Options(
Provide(),
// supply the policies
fx.Supply(MockBundle(policies)),
// provide a bundle server that is easy to use in tests.
fx.Provide(fx.Annotate(NewMockBundles,
fx.OnStart(func(ctx context.Context, a *MockBundles) error { return a.Start(ctx) }),
fx.OnStop(func(ctx context.Context, a *MockBundles) error { return a.Stop(ctx) }),
)),
// provide as bundle server
fx.Provide(func(bs *MockBundles) BundleServer { return bs }),
)
}