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 }