Skip to content

Commit

Permalink
Moved CedarConfig to API package
Browse files Browse the repository at this point in the history
  • Loading branch information
micahhausler committed Nov 27, 2024
1 parent 4202289 commit b00602b
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 219 deletions.
2 changes: 1 addition & 1 deletion PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repo: github.com/awslabs/cedar-access-control-for-k8s
resources:
- api:
crdVersion: v1alpha1
controller: true
controller: false
domain: k8s.aws
group: cedar
kind: Policy
Expand Down
145 changes: 145 additions & 0 deletions api/v1alpha1/config_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package v1alpha1

import (
"encoding/json"
"errors"
"fmt"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
StoreTypeDirectory = "directory"
StoreTypeCRD = "crd"
StoreTypeVerifiedPermissions = "verifiedPermissions"
)

type Duration time.Duration

func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}

func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
*d = Duration(time.Duration(value))
return nil
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return errors.New("invalid duration")
}
}

// +kubebuilder:object:root=true

// CedarConfig is a type for storing Cedar webhook configuration data
type CedarConfig struct {
metav1.TypeMeta `json:",inline"`

//+required
Spec ConfigSpec `json:"spec"`
}

func (c *CedarConfig) Validate() error {
for i, storeDef := range c.Spec.Stores {
storeId := fmt.Sprintf(".spec.stores[%d]: ", i)
err := storeDef.Validate()
if err != nil {
return errors.New(storeId + err.Error())
}
}
return nil
}

type ConfigSpec struct {
//+required
Stores []StoreConfig `json:"stores"`
}

type StoreConfig struct {
//+kubebuilder:validation:Enum=directory;crd;verifiedPermissions
//+required
Type string `json:"type"`
//+optional
DirectoryStore DirectoryStoreConfig `json:"directoryStore,omitempty"`
//+optional
CRDStore CRDStoreConfig `json:"crdStore,omitempty"`
//+optional
VerifiedPermissionsStore VerifiedPermissionsStoreConfig `json:"verifiedPermissionsStore,omitempty"`
}

type DirectoryStoreConfig struct {
//+required
Path string `json:"path"`
//+optional
RefreshInterval *Duration `json:"refreshInterval,omitempty"`
}

type CRDStoreConfig struct {
//+optional
KubeconfigContext string `json:"kubeconfigContext,omitempty"`
}

type VerifiedPermissionsStoreConfig struct {
//+required
PolicyStoreID string `json:"policyStoreId"`
//+optional
RefreshInterval *Duration `json:"refreshInterval,omitempty"`
//+optional
AWSRegion string `json:"awsRegion,omitempty"`
//+optional
AWSProfile string `json:"awsProfile,omitempty"`
}

func (c *StoreConfig) Validate() error {
switch c.Type {
case StoreTypeDirectory:
if c.DirectoryStore.Path == "" {
return errors.New("directory store path is required")
}
if c.DirectoryStore.RefreshInterval != nil {
if *c.DirectoryStore.RefreshInterval < Duration(time.Second*30) {
return errors.New("directory store refresh interval must be at least 30s")
}
if *c.DirectoryStore.RefreshInterval > Duration(time.Hour*24*7) {
return errors.New("directory store refresh interval must be under 1 week (168h)")
}
} else {
defaultDur := Duration(time.Minute * 1)
c.DirectoryStore.RefreshInterval = &defaultDur
}
case StoreTypeCRD:
// no-op
case StoreTypeVerifiedPermissions:
if c.VerifiedPermissionsStore.PolicyStoreID == "" {
return errors.New("verified permissions store policy store id is required")
}
if c.VerifiedPermissionsStore.RefreshInterval != nil {
if *c.VerifiedPermissionsStore.RefreshInterval < Duration(time.Second*30) {
return errors.New("verified permissions refresh interval must be at least 30s")
}
if *c.VerifiedPermissionsStore.RefreshInterval > Duration(time.Hour*24*7) {
return errors.New("verified permissions refresh interval must be under 1 week (168h)")
}
} else {
defaultDur := Duration(time.Minute * 5)
c.VerifiedPermissionsStore.RefreshInterval = &defaultDur
}

default:
return errors.New("invalid store type")
}
return nil
}
80 changes: 80 additions & 0 deletions api/v1alpha1/config_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package v1alpha1_test

import (
"errors"
"testing"
"time"

"github.com/awslabs/cedar-access-control-for-k8s/api/v1alpha1"
)

func TestDurationJson(t *testing.T) {
cases := []struct {
name string
input []byte
skipMarshal bool
want v1alpha1.Duration
wantErr error
}{
{
name: "float64",
input: []byte("60000000000.0"),
skipMarshal: true,
want: v1alpha1.Duration(time.Second * 60),
},
{
name: "Duration string",
input: []byte(`"59m0s"`),
want: v1alpha1.Duration(time.Minute * 59),
},
{
name: "invalid string",
input: []byte(`"60x"`),
wantErr: errors.New(`time: unknown unit "x" in duration "60x"`),
},
{
name: "invalid type",
input: []byte(`true`),
wantErr: errors.New("invalid duration"),
},
{
name: "invalid json",
input: []byte(`{true}`),
wantErr: errors.New("invalid character 't' looking for beginning of object key string"),
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var dur v1alpha1.Duration
err := dur.UnmarshalJSON(tc.input)
if err != nil {
if tc.wantErr != nil {
if err.Error() != tc.wantErr.Error() {
t.Fatalf("UnmarshalJSON() error = '%v', wantErr '%v'", err, tc.wantErr)
}
return
}
t.Fatal(err)
}
if dur != tc.want {
t.Fatalf("got %v, want %v", dur, tc.want)
}

if !tc.skipMarshal {
got, err := dur.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(got) != string(tc.input) {
t.Errorf("MarshalJSON() = %v, want %v", string(got), string(tc.input))
}
}
})
}
}

func DurationPtr(d time.Duration) *v1alpha1.Duration {
dur := v1alpha1.Duration(d)
return &dur
}
1 change: 1 addition & 0 deletions api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ var (

func init() {
SchemeBuilder.Register(&PolicyList{}, &Policy{})
SchemeBuilder.Register(&CedarConfig{})
}
6 changes: 2 additions & 4 deletions api/v1alpha1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type PolicySpec struct {
// Important: Run "make" to regenerate code after modifying this file

// Content is a string representing the policy content
//+required
Content string `json:"content"`
}

Expand All @@ -47,6 +48,7 @@ type Policy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

//+required
Spec PolicySpec `json:"spec"`
Status PolicyStatus `json:"status,omitempty"`
}
Expand All @@ -60,10 +62,6 @@ type PolicyList struct {
Items []Policy `json:"items"`
}

func init() {
SchemeBuilder.Register(&Policy{}, &PolicyList{})
}

// E2ELatencyLog represents the log structure to emit when calculating e2e latency
type E2ELatencyLog struct {
ClusterID string `json:"ClusterId"`
Expand Down
120 changes: 120 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b00602b

Please sign in to comment.