istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/server/ca/authenticate/xfcc_authenticator_test.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 authenticate
    16  
    17  import (
    18  	"context"
    19  	"net"
    20  	"net/http"
    21  	"net/netip"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/alecholmes/xfccparser"
    27  	"google.golang.org/grpc/metadata"
    28  	"google.golang.org/grpc/peer"
    29  
    30  	"istio.io/istio/pkg/security"
    31  )
    32  
    33  func TestIsTrustedAddress(t *testing.T) {
    34  	cases := []struct {
    35  		name    string
    36  		cidr    string
    37  		peer    string
    38  		trusted bool
    39  	}{
    40  		{
    41  			name:    "localhost client with port",
    42  			cidr:    "",
    43  			peer:    "127.0.0.1:9901",
    44  			trusted: true,
    45  		},
    46  		{
    47  			// Should never happen, added test case for testing it.
    48  			name:    "localhost client without port",
    49  			cidr:    "",
    50  			peer:    "127.0.0.1",
    51  			trusted: false,
    52  		},
    53  		{
    54  			name:    "external client without trusted cidr",
    55  			cidr:    "",
    56  			peer:    "172.0.0.1",
    57  			trusted: false,
    58  		},
    59  		{
    60  			name:    "cidr in range",
    61  			cidr:    "172.17.0.0/16,192.17.0.0/16",
    62  			peer:    "172.17.0.2:9901",
    63  			trusted: true,
    64  		},
    65  		{
    66  			name:    "cidr in range with both ipv6 and ipv4",
    67  			cidr:    "172.17.0.0/16,2001:db8:1234:1a00::/56",
    68  			peer:    "[2001:0db8:1234:1a00:0000:0000:0000:0000]:80",
    69  			trusted: true,
    70  		},
    71  		{
    72  			name:    "cidr outside range",
    73  			cidr:    "172.17.0.0/16,172.17.0.0/16",
    74  			peer:    "110.17.0.2",
    75  			trusted: false,
    76  		},
    77  	}
    78  
    79  	for _, tt := range cases {
    80  		t.Run(tt.name, func(t *testing.T) {
    81  			if result := isTrustedAddress(tt.peer, strings.Split(tt.cidr, ",")); result != tt.trusted {
    82  				t.Errorf("Unexpected authentication result: want %v but got %v",
    83  					tt.trusted, result)
    84  			}
    85  		})
    86  	}
    87  }
    88  
    89  func TestXfccAuthenticator(t *testing.T) {
    90  	cases := []struct {
    91  		name               string
    92  		xfccHeader         string
    93  		caller             *security.Caller
    94  		authenticateErrMsg string
    95  		useHttpRequest     bool //nolint
    96  	}{
    97  		{
    98  			name:               "No xfcc header",
    99  			xfccHeader:         "",
   100  			authenticateErrMsg: "caller from 127.0.0.1:2301 does not have Xfcc header",
   101  		},
   102  		{
   103  			name:               "junk xfcc header",
   104  			xfccHeader:         `junk xfcc header`,
   105  			authenticateErrMsg: `error in parsing xfcc header: invalid header format: 0:16: unexpected token "<EOF>" (expected "=" <string>?)`,
   106  		},
   107  		{
   108  			name: "Xfcc Header single hop",
   109  			// nolint lll
   110  			xfccHeader: `Hash=meshclient;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
   111  			caller: &security.Caller{
   112  				AuthSource: security.AuthSourceClientCertificate,
   113  				Identities: []string{
   114  					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
   115  				},
   116  			},
   117  		},
   118  		{
   119  			name: "Xfcc Header multiple hops",
   120  			// nolint lll
   121  			xfccHeader: `Hash=hash;Cert="-----BEGIN%20CERTIFICATE-----%0cert%0A-----END%20CERTIFICATE-----%0A";Subject="CN=hello,OU=hello,O=Acme\, Inc.";URI=spiffe://mesh.example.com/ns/firstns/sa/firstsa;DNS=hello.west.example.com;DNS=hello.east.example.com,By=spiffe://mesh.example.com/ns/hellons/sa/hellosa;Hash=again;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
   122  			caller: &security.Caller{
   123  				AuthSource: security.AuthSourceClientCertificate,
   124  				Identities: []string{
   125  					"spiffe://mesh.example.com/ns/firstns/sa/firstsa",
   126  					"hello.west.example.com",
   127  					"hello.east.example.com",
   128  					"hello",
   129  					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
   130  				},
   131  			},
   132  		},
   133  		{
   134  			name:               "No xfcc header with http",
   135  			xfccHeader:         "",
   136  			authenticateErrMsg: "caller from 127.0.0.1:2301 does not have Xfcc header",
   137  			useHttpRequest:     true,
   138  		},
   139  		{
   140  			name:               "junk xfcc header with http",
   141  			xfccHeader:         `junk xfcc header`,
   142  			authenticateErrMsg: `error in parsing xfcc header: invalid header format: 0:16: unexpected token "<EOF>" (expected "=" <string>?)`,
   143  			useHttpRequest:     true,
   144  		},
   145  		{
   146  			name: "Xfcc Header single hop with http",
   147  			// nolint lll
   148  			xfccHeader: `Hash=meshclient;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
   149  			caller: &security.Caller{
   150  				AuthSource: security.AuthSourceClientCertificate,
   151  				Identities: []string{
   152  					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
   153  				},
   154  			},
   155  			useHttpRequest: true,
   156  		},
   157  		{
   158  			name: "Xfcc Header multiple hops with http",
   159  			// nolint lll
   160  			xfccHeader: `Hash=hash;Cert="-----BEGIN%20CERTIFICATE-----%0cert%0A-----END%20CERTIFICATE-----%0A";Subject="CN=hello,OU=hello,O=Acme\, Inc.";URI=spiffe://mesh.example.com/ns/firstns/sa/firstsa;DNS=hello.west.example.com;DNS=hello.east.example.com,By=spiffe://mesh.example.com/ns/hellons/sa/hellosa;Hash=again;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
   161  			caller: &security.Caller{
   162  				AuthSource: security.AuthSourceClientCertificate,
   163  				Identities: []string{
   164  					"spiffe://mesh.example.com/ns/firstns/sa/firstsa",
   165  					"hello.west.example.com",
   166  					"hello.east.example.com",
   167  					"hello",
   168  					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
   169  				},
   170  			},
   171  			useHttpRequest: true,
   172  		},
   173  	}
   174  
   175  	auth := &XfccAuthenticator{}
   176  
   177  	for _, tt := range cases {
   178  		t.Run(tt.name, func(t *testing.T) {
   179  			var authContext security.AuthContext
   180  			if tt.useHttpRequest {
   181  				httpRequest := http.Request{
   182  					RemoteAddr: "127.0.0.1:2301",
   183  					Header:     map[string][]string{},
   184  				}
   185  				if len(tt.xfccHeader) > 0 {
   186  					httpRequest.Header.Add(xfccparser.ForwardedClientCertHeader, tt.xfccHeader)
   187  				}
   188  				authContext = security.AuthContext{Request: &httpRequest}
   189  			} else {
   190  				addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:2301"))
   191  				ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: addr})
   192  				md := metadata.MD{}
   193  				if len(tt.xfccHeader) > 0 {
   194  					md.Append(xfccparser.ForwardedClientCertHeader, tt.xfccHeader)
   195  				}
   196  				ctx = metadata.NewIncomingContext(ctx, md)
   197  				authContext = security.AuthContext{GrpcContext: ctx}
   198  			}
   199  
   200  			result, err := auth.Authenticate(authContext)
   201  			if len(tt.authenticateErrMsg) > 0 {
   202  				if err == nil {
   203  					t.Errorf("Succeeded. Error expected: %v", err)
   204  				} else if err.Error() != tt.authenticateErrMsg {
   205  					t.Errorf("Incorrect error message: want %q but got %q",
   206  						tt.authenticateErrMsg, err.Error())
   207  				}
   208  			} else if err != nil {
   209  				t.Fatalf("Unexpected Error: %v", err)
   210  			}
   211  
   212  			if !reflect.DeepEqual(tt.caller, result) {
   213  				t.Errorf("Unexpected authentication result: want %v but got %v",
   214  					tt.caller, result)
   215  			}
   216  		})
   217  	}
   218  }