istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/authz/kube.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package authz
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/hashicorp/go-multierror"
    24  
    25  	meshconfig "istio.io/api/mesh/v1alpha1"
    26  	"istio.io/istio/pkg/config/protocol"
    27  	"istio.io/istio/pkg/test/env"
    28  	"istio.io/istio/pkg/test/framework/components/echo"
    29  	"istio.io/istio/pkg/test/framework/components/istio"
    30  	"istio.io/istio/pkg/test/framework/components/namespace"
    31  	"istio.io/istio/pkg/test/framework/resource"
    32  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    33  	"istio.io/istio/pkg/test/framework/resource/config/cleanup"
    34  	"istio.io/istio/pkg/test/kube"
    35  	"istio.io/istio/pkg/test/scopes"
    36  	"istio.io/istio/pkg/test/util/tmpl"
    37  	"istio.io/istio/pkg/test/util/yml"
    38  	"istio.io/istio/pkg/util/protomarshal"
    39  	"istio.io/istio/pkg/util/sets"
    40  )
    41  
    42  const (
    43  	httpName = "ext-authz-http"
    44  	grpcName = "ext-authz-grpc"
    45  	httpPort = 8000
    46  	grpcPort = 9000
    47  
    48  	providerTemplate = `
    49  extensionProviders:
    50  - name: "{{ .httpName }}"
    51    envoyExtAuthzHttp:
    52      service: "{{ .fqdn }}"
    53      port: {{ .httpPort }}
    54      headersToUpstreamOnAllow: ["x-ext-authz-*"]
    55      headersToDownstreamOnDeny: ["x-ext-authz-*"]
    56      includeRequestHeadersInCheck: ["x-ext-authz"]
    57      includeAdditionalHeadersInCheck:
    58        x-ext-authz-additional-header-new: additional-header-new-value
    59        x-ext-authz-additional-header-override: additional-header-override-value
    60  - name: "{{ .grpcName }}"
    61    envoyExtAuthzGrpc:
    62      service: "{{ .fqdn }}"
    63      port: {{ .grpcPort }}`
    64  )
    65  
    66  var _ resource.Resource = &serverImpl{}
    67  
    68  func newKubeServer(ctx resource.Context, ns namespace.Instance) (server *serverImpl, err error) {
    69  	start := time.Now()
    70  	scopes.Framework.Info("=== BEGIN: Deploy authz server ===")
    71  	defer func() {
    72  		if err != nil {
    73  			scopes.Framework.Error("=== FAILED: Deploy authz server ===")
    74  			scopes.Framework.Error(err)
    75  		} else {
    76  			scopes.Framework.Infof("=== SUCCEEDED: Deploy authz server in %v ===", time.Since(start))
    77  		}
    78  	}()
    79  
    80  	// Create the namespace, if unspecified.
    81  	if ns == nil {
    82  		ns, err = namespace.New(ctx, namespace.Config{
    83  			Prefix: "authz",
    84  			Inject: true,
    85  		})
    86  		if err != nil {
    87  			return
    88  		}
    89  	}
    90  
    91  	server = &serverImpl{
    92  		ns: ns,
    93  		providers: []Provider{
    94  			&providerImpl{
    95  				name: httpName,
    96  				api:  HTTP,
    97  				protocolSupported: func(p protocol.Instance) bool {
    98  					// HTTP protocol doesn't support raw TCP requests.
    99  					return !p.IsTCP()
   100  				},
   101  				targetSupported: func(echo.Target) bool {
   102  					return true
   103  				},
   104  				check: checkHTTP,
   105  			},
   106  			&providerImpl{
   107  				name: grpcName,
   108  				api:  GRPC,
   109  				protocolSupported: func(protocol.Instance) bool {
   110  					return true
   111  				},
   112  				targetSupported: func(echo.Target) bool {
   113  					return true
   114  				},
   115  				check: checkGRPC,
   116  			},
   117  		},
   118  	}
   119  	server.id = ctx.TrackResource(server)
   120  
   121  	// Deploy the authz server.
   122  	if err = server.deploy(ctx); err != nil {
   123  		return
   124  	}
   125  
   126  	// Patch MeshConfig to install the providers.
   127  	err = server.installProviders(ctx)
   128  	return
   129  }
   130  
   131  func readDeploymentYAML(ctx resource.Context) (string, error) {
   132  	// Read the samples file.
   133  	filePath := fmt.Sprintf("%s/samples/extauthz/ext-authz.yaml", env.IstioSrc)
   134  	data, err := os.ReadFile(filePath)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  	yamlText := string(data)
   139  	// Replace the image.
   140  	s := ctx.Settings().Image
   141  	if s.PullSecret != "" {
   142  		var imageSpec resource.ImageSettings
   143  		imageSpec.PullSecret = s.PullSecret
   144  		secretName, err := imageSpec.PullSecretName()
   145  		if err != nil {
   146  			return "", err
   147  		}
   148  		yamlText, err = addPullSecret(yamlText, secretName)
   149  		if err != nil {
   150  			return "", err
   151  		}
   152  	}
   153  
   154  	oldImage := "gcr.io/istio-testing/ext-authz:latest"
   155  	newImage := fmt.Sprintf("%s/ext-authz:%s", s.Hub, s.Tag)
   156  	yamlText = strings.ReplaceAll(yamlText, oldImage, newImage)
   157  
   158  	// Replace the image pull policy
   159  	oldPolicy := "IfNotPresent"
   160  	newPolicy := s.PullPolicy
   161  	yamlText = strings.ReplaceAll(yamlText, oldPolicy, newPolicy)
   162  
   163  	return yamlText, nil
   164  }
   165  
   166  func addPullSecret(resource string, pullSecret string) (string, error) {
   167  	res := yml.SplitString(resource)
   168  	updatedYaml, err := yml.ApplyPullSecret(res[1], pullSecret)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	mergedYaml := yml.JoinString(res[0], updatedYaml)
   173  	return mergedYaml, nil
   174  }
   175  
   176  func (s *serverImpl) deploy(ctx resource.Context) error {
   177  	yamlText, err := readDeploymentYAML(ctx)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	if err := ctx.ConfigKube(ctx.Clusters()...).
   183  		YAML(s.ns.Name(), yamlText).
   184  		Apply(apply.CleanupConditionally); err != nil {
   185  		return err
   186  	}
   187  
   188  	// Wait for the endpoints to be ready.
   189  	var g multierror.Group
   190  	for _, c := range ctx.Clusters() {
   191  		c := c
   192  		g.Go(func() error {
   193  			_, _, err := kube.WaitUntilServiceEndpointsAreReady(c.Kube(), s.ns.Name(), "ext-authz")
   194  			return err
   195  		})
   196  	}
   197  
   198  	return g.Wait().ErrorOrNil()
   199  }
   200  
   201  func (s *serverImpl) installProviders(ctx resource.Context) error {
   202  	// Update the mesh config extension provider for the ext-authz service.
   203  	providerYAML, err := tmpl.Evaluate(providerTemplate, s.templateArgs())
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	return installProviders(ctx, providerYAML)
   209  }
   210  
   211  type serverImpl struct {
   212  	id        resource.ID
   213  	ns        namespace.Instance
   214  	providers []Provider
   215  }
   216  
   217  func (s *serverImpl) ID() resource.ID {
   218  	return s.id
   219  }
   220  
   221  func (s *serverImpl) Namespace() namespace.Instance {
   222  	return s.ns
   223  }
   224  
   225  func (s *serverImpl) Providers() []Provider {
   226  	return append([]Provider{}, s.providers...)
   227  }
   228  
   229  func (s *serverImpl) templateArgs() map[string]any {
   230  	fqdn := fmt.Sprintf("ext-authz.%s.svc.cluster.local", s.ns.Name())
   231  	return map[string]any{
   232  		"fqdn":     fqdn,
   233  		"httpName": httpName,
   234  		"grpcName": grpcName,
   235  		"httpPort": httpPort,
   236  		"grpcPort": grpcPort,
   237  	}
   238  }
   239  
   240  func installProviders(ctx resource.Context, providerYAML string) error {
   241  	var ist istio.Instance
   242  	ist, err := istio.Get(ctx)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	// Now parse the provider YAML.
   248  	newMC := &meshconfig.MeshConfig{}
   249  	if err := protomarshal.ApplyYAML(providerYAML, newMC); err != nil {
   250  		return err
   251  	}
   252  
   253  	providerNames := sets.New[string]()
   254  	for _, p := range newMC.GetExtensionProviders() {
   255  		providerNames.Insert(p.Name)
   256  	}
   257  
   258  	return ist.UpdateMeshConfig(ctx,
   259  		func(mc *meshconfig.MeshConfig) error {
   260  			newProviders := []*meshconfig.MeshConfig_ExtensionProvider{}
   261  			// Merge the extension providers.
   262  			// If we are overwriting an existing one, keep the new one.
   263  			for _, o := range mc.ExtensionProviders {
   264  				if !providerNames.Contains(o.Name) {
   265  					newProviders = append(newProviders, o)
   266  				}
   267  			}
   268  			newProviders = append(newProviders, newMC.ExtensionProviders...)
   269  			mc.ExtensionProviders = newProviders
   270  			return nil
   271  		}, cleanup.Conditionally)
   272  }