Skip to content

Commit

Permalink
WIP: switch trigger sa based auth to impersonate
Browse files Browse the repository at this point in the history
  • Loading branch information
gabemontero committed Aug 10, 2020
1 parent b6d0aac commit da10d43
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ rules:
verbs: ["get"]
- apiGroups: [""]
# secrets are only needed for Github/Gitlab interceptors, serviceaccounts only for per trigger authorization
resources: ["configmaps", "secrets", "serviceaccounts"]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "pipelineresources", "taskruns"]
verbs: ["create"]
#- apiGroups: [""]
# resources: ["users", "groups", "serviceaccounts"]
# verbs: ["impersonate"]
17 changes: 6 additions & 11 deletions pkg/sink/auth_override.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package sink

import (
"fmt"
triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1"
dynamicClientset "github.com/tektoncd/triggers/pkg/client/dynamic/clientset"
"github.com/tektoncd/triggers/pkg/client/dynamic/clientset/tekton"
Expand All @@ -34,7 +35,7 @@ import (
//REST config used to build those client. The other non-credential related parameters for the
//REST client used are copied from the in cluster config of the event sink.
type AuthOverride interface {
OverrideAuthentication(token string,
OverrideAuthentication(sa *corev1.ObjectReference,
log *zap.SugaredLogger,
defaultDiscoveryClient discoveryclient.ServerResourcesInterface,
defaultDynamicClient dynamic.Interface) (discoveryClient discoveryclient.ServerResourcesInterface,
Expand Down Expand Up @@ -109,18 +110,10 @@ func (r Sink) retrieveAuthToken(saRef *corev1.ObjectReference, eventLog *zap.Sug
return "", savedErr
}

func newConfig(newToken string, config *rest.Config) *rest.Config {
// first clean out all user credentials from the pods' in cluster config
newConfig := rest.AnonymousClientConfig(config)
// add the token from our interceptors
newConfig.BearerToken = newToken
return newConfig
}

type DefaultAuthOverride struct {
}

func (r DefaultAuthOverride) OverrideAuthentication(token string,
func (r DefaultAuthOverride) OverrideAuthentication(sa *corev1.ObjectReference,
log *zap.SugaredLogger,
defaultDiscoverClient discoveryclient.ServerResourcesInterface,
defaultDynamicClient dynamic.Interface) (discoveryClient discoveryclient.ServerResourcesInterface,
Expand All @@ -133,7 +126,9 @@ func (r DefaultAuthOverride) OverrideAuthentication(token string,
log.Errorf("overrideAuthentication: problem getting in cluster config: %#v\n", err)
return
}
clusterConfig = newConfig(token, clusterConfig)
clusterConfig.Impersonate = rest.ImpersonationConfig{
UserName: fmt.Sprintf("system:serviceaccount:%s:%s", sa.Namespace, sa.Name),
}
dc, err := dynamic.NewForConfig(clusterConfig)
if err != nil {
log.Errorf("overrideAuthentication: problem getting dynamic client set: %#v\n", err)
Expand Down
13 changes: 7 additions & 6 deletions pkg/sink/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/tektoncd/triggers/pkg/resources"
"github.com/tektoncd/triggers/pkg/template"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
discoveryclient "k8s.io/client-go/discovery"
Expand Down Expand Up @@ -164,12 +165,12 @@ func (r Sink) processTrigger(t *triggersv1.EventListenerTrigger, request *http.R

log.Infof("ResolvedParams : %+v", params)
resources := template.ResolveResources(rt.TriggerTemplate, params)
token, err := r.retrieveAuthToken(t.ServiceAccount, eventLog)
/*token, err := r.retrieveAuthToken(t.ServiceAccount, eventLog)
if err != nil {
log.Error(err)
return err
}
if err := r.CreateResources(token, resources, t.Name, eventID, log); err != nil {
}*/
if err := r.CreateResources(t.ServiceAccount, resources, t.Name, eventID, log); err != nil {
log.Error(err)
return err
}
Expand Down Expand Up @@ -233,17 +234,17 @@ func (r Sink) ExecuteInterceptors(t *triggersv1.EventListenerTrigger, in *http.R
return payload, resp.Header, nil
}

func (r Sink) CreateResources(token string, res []json.RawMessage, triggerName, eventID string, log *zap.SugaredLogger) error {
func (r Sink) CreateResources(sa *corev1.ObjectReference, res []json.RawMessage, triggerName, eventID string, log *zap.SugaredLogger) error {
discoveryClient := r.DiscoveryClient
dynamicClient := r.DynamicClient
var err error
if len(token) > 0 {
if sa != nil {
// So at start up the discovery and dynamic clients are created using the in cluster config
// of this pod (i.e. using the credentials of the serviceaccount associated with the EventListener)

// However, we also have a ServiceAccount/Secret/SecretKey reference with each EventListenerTrigger to allow
// for more fine grained authorization control around the resources we create below.
discoveryClient, dynamicClient, err = r.Auth.OverrideAuthentication(token, log, r.DiscoveryClient, r.DynamicClient)
discoveryClient, dynamicClient, err = r.Auth.OverrideAuthentication(sa, log, r.DiscoveryClient, r.DynamicClient)
if err != nil {
log.Errorf("problem cloning rest config: %#v", err)
return err
Expand Down
6 changes: 3 additions & 3 deletions pkg/sink/sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,18 +851,18 @@ type fakeAuth struct {

var triggerAuthWG sync.WaitGroup

func (r fakeAuth) OverrideAuthentication(token string,
func (r fakeAuth) OverrideAuthentication(sa *corev1.ObjectReference,
log *zap.SugaredLogger,
defaultDiscoverClient discoveryclient.ServerResourcesInterface,
defaultDynamicClient dynamic.Interface) (discoveryClient discoveryclient.ServerResourcesInterface,
dynamicClient dynamic.Interface,
err error) {

if token == userWithoutPermissions {
if sa.Name == userWithoutPermissions {
dynamicClient := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme())
dynamicClient.PrependReactor("*", "*", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
defer triggerAuthWG.Done()
return true, nil, kerrors.NewUnauthorized(token + " unauthorized")
return true, nil, kerrors.NewUnauthorized(sa.Name + " unauthorized")
})
dynamicSet := dynamicclientset.New(tekton.WithClient(dynamicClient))
return defaultDiscoverClient, dynamicSet, nil
Expand Down
52 changes: 52 additions & 0 deletions test/eventlistener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"k8s.io/client-go/kubernetes"
"net/http"
"net/url"
"path/filepath"
Expand Down Expand Up @@ -66,6 +67,55 @@ func loadExamplePREventBytes() ([]byte, error) {
return bytes, nil
}

func impersonateRBAC(t *testing.T, sa, namespace string, kubeClient kubernetes.Interface) {
impersonateName := fmt.Sprintf("impersonate-%s-defaultSA", namespace)
role := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{Name: impersonateName, Namespace: namespace},
Rules: []rbacv1.PolicyRule{
{
Verbs: []string{"impersonate"},
APIGroups: []string{""},
Resources: []string{"users", "groups", "serviceaccounts"},
ResourceNames: nil,
NonResourceURLs: nil,
},
},
}
_, err := kubeClient.RbacV1().Roles(namespace).Get(impersonateName, metav1.GetOptions{})
if err == nil || errors.IsNotFound(err) {
_, err := kubeClient.RbacV1().Roles(namespace).Create(role)
if err != nil {
t.Fatalf("impersonate role creation failed namespace %q: %s", namespace, err)
}
} else {
t.Fatalf("Pre-check for impersonate failed namespace %q: %s", namespace, err)
}
roleBinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{Name: impersonateName, Namespace: namespace},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: sa,
Namespace: namespace,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: impersonateName,
},
}
_, err = kubeClient.RbacV1().RoleBindings(namespace).Get(impersonateName, metav1.GetOptions{})
if err == nil || errors.IsNotFound(err) {
_, err := kubeClient.RbacV1().RoleBindings(namespace).Create(roleBinding)
if err != nil {
t.Fatalf("View rolebinding creation failed namespace %q: %s", namespace, err)
}
} else {
t.Fatalf("Pre-check for view rolebinding failed namespace %q: %s", namespace, err)
}
}

func TestEventListenerCreate(t *testing.T) {
c, namespace := setup(t)
t.Parallel()
Expand Down Expand Up @@ -222,6 +272,8 @@ func TestEventListenerCreate(t *testing.T) {
t.Fatalf("Error creating RoleBinding: %s", err)
}

impersonateRBAC(t, sa.Name, namespace, c.KubeClient)

// EventListener
el, err := c.TriggersClient.TriggersV1alpha1().EventListeners(namespace).Create(
bldr.EventListener("my-eventlistener", namespace,
Expand Down

0 comments on commit da10d43

Please sign in to comment.