Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hooksie1 committed Jan 1, 2022
0 parents commit c897fb7
Show file tree
Hide file tree
Showing 33 changed files with 3,150 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmsnrctl
cmsnrctl.exe
cmsnrctl-darwin
.idea
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:alpine as builder
WORKDIR /app
RUN apk update && apk upgrade && apk add --no-cache ca-certificates
RUN update-ca-certificates
ADD . /app/
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w" -installsuffix cgo -o cmsnrctl .


FROM scratch

COPY --from=builder /app/cmsnrctl .
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ENTRYPOINT ["./cmsnrctl"]
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
PROJECT_NAME := "cmsnr"
PKG := "gitlab.com/hooksie1/$(PROJECT_NAME)"
PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
VERSION := $$(git describe --tags | cut -d '-' -f 1)


.PHONY: all build docker dep clean test coverage lint

all: build

lint: ## Lint the files
@golint -set_exit_status ./...

test: ## Run unittests
@go test ./...

coverage:
@go test -cover ./...
@go test ./... -coverprofile=cover.out && go tool cover -html=cover.out -o coverage.html

dep: ## Get the dependencies
@go get -u golang.org/x/lint/golint

build: linux windows mac

linux: dep
CGO_ENABLED=0 GOOS=linux go build -a -ldflags "-w -X '$(PKG)/cmd.Version=$(VERSION)'" -o $(PROJECT_NAME)ctl

windows: dep
CGO_ENABLED=0 GOOS=windows go build -a -ldflags "-w -X '$(PKG)/cmd.Version=$(VERSION)'" -o $(PROJECT_NAME)ctl.exe

mac: dep
CGO_ENABLED=0 GOOS=darwin go build -a -ldflags "-w -X '$(PKG)/cmd.Version=$(VERSION)'" -o $(PROJECT_NAME)ctl-darwin


clean: ## Remove previous build
git clean -fd
git clean -fx
git reset --hard

help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# cmsnr

## Description
cmsnr (pronounced "commissioner") is a lightweight framework for running OPA in a sidecar alongside your applications in Kubernetes.

## Purpose
This project gives Kubernetes users a simple way to deploy OPA policies for their apps. It has the ability to define which
applications should have which policies, and allows for adding multiple policies into the same sidecar. When a policy is added
or updated in the cluster, the client in each sidcar will check if the deployment name matches the name in their own deployment.
If the name is a match each client then uploads that policy into it's own OPA giving that Kubernetes deployment access to that policy.

## OPA Policy CRD
cmsnr uses an OPA policy CRD to store the Rego policy in the cluster. The CRD also takes a deployment name and a policy name.
The deployment name should match the deployment name in the pod annotation for the deployment/pod where you want the policy to
be available. The policy name is the name cmsnr will use when putting the policy in OPA.

## Client
cmsnr uses the cli tool `cmsnrctl` to do all of it's work. It contains a lightweight client that will watch the cluster for new and
updated OPA policies and update them in the corresponding deployments.

## Pod Labels
cmsnr uses a mutating webhook to watch for pods with the annotation `cmsnr.com/inject: enabled`. Cmsnr will then inject two lightweight containers
in the pod: OPA and cmsnr itself. It injects the statically linked OPA container and cmsnr itself is just a statically linked binary.

## Deploy cmsnr

To deploy cmsnr, first download the most recent version from the releases page. Then simply run
`cmsnrctl server deploy | kubectl apply -f -`

## Examples
To see the functionality of cmsnr, run download the most recent version. Then run
`cmsnrctl server deploy | kubectl apply -f -`. Then run `kubectl apply -f examples/`

This will create an annotated deployment and two OPA policies which will be injected into the sidecar.
77 changes: 77 additions & 0 deletions api/v1alpha1/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package v1alpha1

import (
"context"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)

type OpaPolicyInterface interface {
List(opts metav1.ListOptions) (*OpaPolicyList, error)
Get(name string, options metav1.GetOptions) (*OpaPolicy, error)
Create(*OpaPolicy) (*OpaPolicy, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
}

type opaPolicyClient struct {
restClient rest.Interface
ns string
}

func (c *opaPolicyClient) List(opts metav1.ListOptions) (*OpaPolicyList, error) {
result := OpaPolicyList{}
ctx := context.Background()
err := c.restClient.
Get().
Namespace(c.ns).
Resource("opapolicies").
VersionedParams(&opts, scheme.ParameterCodec).
Do(ctx).
Into(&result)

return &result, err
}

func (c *opaPolicyClient) Get(name string, opts metav1.GetOptions) (*OpaPolicy, error) {
result := OpaPolicy{}
ctx := context.Background()
err := c.restClient.
Get().
Namespace(c.ns).
Name(name).
VersionedParams(&opts, scheme.ParameterCodec).
Do(ctx).
Into(&result)

return &result, err
}

func (c *opaPolicyClient) Create(opapolicy *OpaPolicy) (*OpaPolicy, error) {
result := OpaPolicy{}
ctx := context.Background()
err := c.restClient.
Post().
Namespace(c.ns).
Resource("opapolicies").
Body(opapolicy).
Do(ctx).
Into(&result)

return &result, err

}

func (c *opaPolicyClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
opts.Watch = true
ctx := context.Background()
return c.restClient.
Get().
Namespace(c.ns).
Resource("opapolicies").
VersionedParams(&opts, scheme.ParameterCodec).
Watch(ctx)

}
49 changes: 49 additions & 0 deletions api/v1alpha1/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package v1alpha1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)

type OpaV1Alpha1Interface interface {
OpaPolicies(namespace string) OpaPolicyInterface
}

type OpaV1Alpha1Client struct {
restClient rest.Interface
}

func NewForConfig(c *rest.Config) (*OpaV1Alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}

client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}

return &OpaV1Alpha1Client{restClient: client}, nil
}

func (c *OpaV1Alpha1Client) OpaPolicies(namespace string) OpaPolicyInterface {
return &opaPolicyClient{
restClient: c.restClient,
ns: namespace,
}
}

func setConfigDefaults(config *rest.Config) error {
gv := &schema.GroupVersion{Group: CRDGroup, Version: CRDVersion}
config.ContentConfig.GroupVersion = gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()

if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}

return nil
}
30 changes: 30 additions & 0 deletions api/v1alpha1/opa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package v1alpha1

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

// OpaPolicySpec defines the desired state of the OPAPolicy
type OpaPolicySpec struct {
DeploymentName string `json:"deploymentName"`

PolicyName string `json:"policyName"`

Policy string `json:"policy"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// OpaPolicy is the Schema for the OpaPolicy
type OpaPolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec OpaPolicySpec `json:"spec"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// OpaPolicyList contains a list of OpaPolicies
type OpaPolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []OpaPolicy `json:"items"`
}
33 changes: 33 additions & 0 deletions api/v1alpha1/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
CRDGroup string = "cmsnr.com"
CRDVersion string = "v1alpha1"
)

var SchemeGroupVersion = schema.GroupVersion{Group: CRDGroup, Version: CRDVersion}

var (
SchemeBuilder = runtime.NewSchemeBuilder(BuildScheme)
AddToScheme = SchemeBuilder.AddToScheme
)

func BuildScheme(scheme *runtime.Scheme) error {
s := schema.GroupVersion{
Group: CRDGroup,
Version: CRDVersion,
}
scheme.AddKnownTypes(s,
&OpaPolicy{},
&OpaPolicyList{},
)

metav1.AddToGroupVersion(scheme, s)
return nil
}
67 changes: 67 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 c897fb7

Please sign in to comment.