Skip to content

Commit

Permalink
Allow SPIRE configuration through opts
Browse files Browse the repository at this point in the history
Signed-off-by: Brandon Lum <[email protected]>
  • Loading branch information
lumjjb committed Feb 9, 2022
1 parent f7578f3 commit 8296954
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 153 deletions.
5 changes: 5 additions & 0 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func main() {
flag.StringVar(&opts.Images.ImageDigestExporterImage, "imagedigest-exporter-image", "", "The container image containing our image digest exporter binary.")
flag.StringVar(&opts.Images.WorkingDirInitImage, "workingdirinit-image", "", "The container image containing our working dir init binary.")

flag.StringVar(&opts.SpireConfig.TrustDomain, "spire-trust-domain", "example.org", "Experimental: The SPIRE Trust domain to use.")
flag.StringVar(&opts.SpireConfig.SocketPath, "spire-socket-path", "/spiffe-workload-api/spire-agent.sock", "Experimental: The SPIRE agent socket for SPIFFE workload API.")
flag.StringVar(&opts.SpireConfig.ServerAddr, "spire-server-addr", "spire-server.spire.svc.cluster.local:8081", "Experimental: The SPIRE server address for workload/node registration.")
flag.StringVar(&opts.SpireConfig.NodeAliasPrefix, "spire-node-alias-prefix", "/tekton-node/", "Experimental: The SPIRE node alias prefix to use.")

// This parses flags.
cfg := injection.ParseAndGetRESTConfigOrDie()

Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/pipeline/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ limitations under the License.

package pipeline

import (
spireconfig "github.com/tektoncd/pipeline/pkg/spire/config"
)

// Options holds options passed to the Tekton Pipeline controllers
// typically via command-line flags.
type Options struct {
Images Images
Images Images
SpireConfig spireconfig.SpireConfig
}
1 change: 1 addition & 0 deletions pkg/reconciler/taskrun/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func NewController(opts *pipeline.Options, clock clock.Clock) func(context.Conte
KubeClientSet: kubeclientset,
PipelineClientSet: pipelineclientset,
Images: opts.Images,
SpireConfig: opts.SpireConfig,
Clock: clock,
taskRunLister: taskRunInformer.Lister(),
resourceLister: resourceInformer.Lister(),
Expand Down
157 changes: 5 additions & 152 deletions pkg/reconciler/taskrun/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,9 @@ import (
"strings"

"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"

entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1"
spiffetypes "github.com/spiffe/spire-api-sdk/proto/spire/api/types"

"github.com/ghodss/yaml"
"github.com/hashicorp/go-multierror"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/workloadapi"
"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/pod"
Expand All @@ -55,6 +47,8 @@ import (
"github.com/tektoncd/pipeline/pkg/reconciler/events/cloudevent"
"github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources"
"github.com/tektoncd/pipeline/pkg/reconciler/volumeclaim"
"github.com/tektoncd/pipeline/pkg/spire"
spireconfig "github.com/tektoncd/pipeline/pkg/spire/config"
"github.com/tektoncd/pipeline/pkg/taskrunmetrics"
_ "github.com/tektoncd/pipeline/pkg/taskrunmetrics/fake" // Make sure the taskrunmetrics are setup
"github.com/tektoncd/pipeline/pkg/workspace"
Expand All @@ -76,6 +70,7 @@ type Reconciler struct {
KubeClientSet kubernetes.Interface
PipelineClientSet clientset.Interface
Images pipeline.Images
SpireConfig spireconfig.SpireConfig
Clock clock.Clock

// listers index properties about resources
Expand Down Expand Up @@ -436,8 +431,8 @@ func (c *Reconciler) reconcile(ctx context.Context, tr *v1beta1.TaskRun, rtr *re

if podconvert.SidecarsReady(pod.Status) {
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableSpire {
logger.Warnf("LUMJJB registering SPIRE entry: %v/%v", pod.Namespace, pod.Name)
spiffeclient, err := NewSpiffeServerApiClient(ctx)
logger.Infof("Registering SPIRE entry: %v/%v", pod.Namespace, pod.Name)
spiffeclient, err := spire.NewSpiffeServerApiClient(ctx, c.SpireConfig)
if err != nil {
logger.Errorf("Failed to establish client with SPIRE server: %v", err)
return err
Expand Down Expand Up @@ -835,145 +830,3 @@ func willOverwritePodSetAffinity(taskRun *v1beta1.TaskRun) bool {
}
return taskRun.Annotations[workspace.AnnotationAffinityAssistantName] != "" && podTemplate.Affinity != nil
}

type SpiffeServerApiClient struct {
serverConn *grpc.ClientConn
workloadConn *workloadapi.X509Source
entryClient entryv1.EntryClient
}

func NewSpiffeServerApiClient(ctx context.Context) (*SpiffeServerApiClient, error) {
// Create X509Source
// TODO(lumjjb) make sock configurable
source, err := workloadapi.NewX509Source(ctx, workloadapi.WithClientOptions(workloadapi.WithAddr("unix:///spiffe-workload-api/spire-agent.sock")))
if err != nil {
return nil, fmt.Errorf("Unable to create X509Source for SPIFFE client: %w", err)
}

// Create connection
tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny())
conn, err := grpc.DialContext(ctx, "spire-server.spire.svc.cluster.local:8081", grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
source.Close()
return nil, fmt.Errorf("Unable to dial SPIRE server: %w", err)
}

return &SpiffeServerApiClient{
serverConn: conn,
workloadConn: source,
entryClient: entryv1.NewEntryClient(conn),
}, nil
}

func (sc *SpiffeServerApiClient) CreateNodeEntry(ctx context.Context, nodeName string) error {
selectors := []*spiffetypes.Selector{
{
Type: "k8s_psat",
// TODO: set var
Value: "agent_ns:spire",
},
{
Type: "k8s_psat",
Value: "agent_node_name:" + nodeName,
},
}

// TODO(LUMJJB) take in trust domain
entries := []*spiffetypes.Entry{
{
SpiffeId: &spiffetypes.SPIFFEID{
TrustDomain: "example.org",
Path: fmt.Sprintf("/tekton-node/%v", nodeName),
},
ParentId: &spiffetypes.SPIFFEID{
TrustDomain: "example.org",
Path: "/spire/server",
},
Selectors: selectors,
},
}

req := entryv1.BatchCreateEntryRequest{
Entries: entries,
}

resp, err := sc.entryClient.BatchCreateEntry(ctx, &req)
if err != nil {
return err
}

if len(resp.Results) != 1 {
return fmt.Errorf("Batch create entry failed, malformed response expected 1 result")
}

res := resp.Results[0]
if codes.Code(res.Status.Code) == codes.AlreadyExists ||
codes.Code(res.Status.Code) == codes.OK {
return nil
}

return fmt.Errorf("Batch create entry failed, code: %v", res.Status.Code)

}

func (sc *SpiffeServerApiClient) CreateWorkloadEntry(ctx context.Context, tr *v1beta1.TaskRun, pod *corev1.Pod) error {
// We can potentially add attestation on the container images as well since
// the information is available here.
selectors := []*spiffetypes.Selector{
{
Type: "k8s",
Value: "pod-uid:" + string(pod.UID),
},
{
Type: "k8s",
Value: "pod-name:" + pod.Name,
},
}

// TODO(LUMJJB) take in trust domain
entries := []*spiffetypes.Entry{
{
SpiffeId: &spiffetypes.SPIFFEID{
TrustDomain: "example.org",
Path: fmt.Sprintf("/ns/%v/taskrun/%v", tr.Namespace, tr.Name),
},
ParentId: &spiffetypes.SPIFFEID{
TrustDomain: "example.org",
Path: fmt.Sprintf("/tekton-node/%v", pod.Spec.NodeName),
},
Selectors: selectors,
},
}

req := entryv1.BatchCreateEntryRequest{
Entries: entries,
}

resp, err := sc.entryClient.BatchCreateEntry(ctx, &req)
if err != nil {
return err
}

if len(resp.Results) != 1 {
return fmt.Errorf("Batch create entry failed, malformed response expected 1 result")
}

res := resp.Results[0]
if codes.Code(res.Status.Code) == codes.AlreadyExists ||
codes.Code(res.Status.Code) == codes.OK {
return nil
}

return fmt.Errorf("Batch create entry failed, code: %v", res.Status.Code)
}

func (sc *SpiffeServerApiClient) Close() {
err := sc.serverConn.Close()
if err != nil {
// Log error
}
err = sc.workloadConn.Close()
if err != nil {
// Log error
}
}
64 changes: 64 additions & 0 deletions pkg/spire/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"fmt"
"sort"
"strings"
)

// SpireConfig holds the images reference for a number of container images used
// across tektoncd pipelines.
// Example:
// "trustDomain": "example.org",
// "socketPath": "/spiffe-workload-api/spire-agent.sock",
// "serverAddr": "spire-server.spire.svc.cluster.local:8081",
// "nodeAliasPrefix": "/tekton-node/"
type SpireConfig struct {
TrustDomain string
SocketPath string
ServerAddr string
NodeAliasPrefix string
}

// Validate returns an error if any image is not set.
func (c SpireConfig) Validate() error {
var unset []string
for _, f := range []struct {
v, name string
}{
{c.TrustDomain, "spire-trust-domain"},
{c.SocketPath, "spire-socket-path"},
{c.ServerAddr, "spire-server-addr"},
{c.NodeAliasPrefix, "spire-node-alias-prefix"},
} {
if f.v == "" {
unset = append(unset, f.name)
}
}
if len(unset) > 0 {
sort.Strings(unset)
return fmt.Errorf("found unset image flags: %s", unset)
}

if !strings.HasPrefix(c.NodeAliasPrefix, "/") {
return fmt.Errorf("Spire node alias should start with a /")
}

return nil
}
Loading

0 comments on commit 8296954

Please sign in to comment.