1package login 2 3import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/consul/agent" 11 "github.com/hashicorp/consul/agent/consul/authmethod/kubeauth" 12 "github.com/hashicorp/consul/agent/consul/authmethod/testauth" 13 "github.com/hashicorp/consul/api" 14 "github.com/hashicorp/consul/command/acl" 15 "github.com/hashicorp/consul/sdk/testutil" 16 "github.com/hashicorp/consul/testrpc" 17 "github.com/mitchellh/cli" 18 "github.com/stretchr/testify/require" 19) 20 21func TestLoginCommand_noTabs(t *testing.T) { 22 t.Parallel() 23 24 if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { 25 t.Fatal("help has tabs") 26 } 27} 28 29func TestLoginCommand(t *testing.T) { 30 t.Parallel() 31 32 testDir := testutil.TempDir(t, "acl") 33 defer os.RemoveAll(testDir) 34 35 a := agent.NewTestAgent(t, t.Name(), ` 36 primary_datacenter = "dc1" 37 acl { 38 enabled = true 39 tokens { 40 master = "root" 41 } 42 }`) 43 44 defer a.Shutdown() 45 testrpc.WaitForLeader(t, a.RPC, "dc1") 46 47 client := a.Client() 48 49 t.Run("method is required", func(t *testing.T) { 50 ui := cli.NewMockUi() 51 cmd := New(ui) 52 53 args := []string{ 54 "-http-addr=" + a.HTTPAddr(), 55 "-token=root", 56 } 57 58 code := cmd.Run(args) 59 require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) 60 require.Contains(t, ui.ErrorWriter.String(), "Missing required '-method' flag") 61 }) 62 63 tokenSinkFile := filepath.Join(testDir, "test.token") 64 65 t.Run("token-sink-file is required", func(t *testing.T) { 66 ui := cli.NewMockUi() 67 cmd := New(ui) 68 69 args := []string{ 70 "-http-addr=" + a.HTTPAddr(), 71 "-token=root", 72 "-method=test", 73 } 74 75 code := cmd.Run(args) 76 require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) 77 require.Contains(t, ui.ErrorWriter.String(), "Missing required '-token-sink-file' flag") 78 }) 79 80 t.Run("bearer-token-file is required", func(t *testing.T) { 81 defer os.Remove(tokenSinkFile) 82 83 ui := cli.NewMockUi() 84 cmd := New(ui) 85 86 args := []string{ 87 "-http-addr=" + a.HTTPAddr(), 88 "-token=root", 89 "-method=test", 90 "-token-sink-file", tokenSinkFile, 91 } 92 93 code := cmd.Run(args) 94 require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) 95 require.Contains(t, ui.ErrorWriter.String(), "Missing required '-bearer-token-file' flag") 96 }) 97 98 bearerTokenFile := filepath.Join(testDir, "bearer.token") 99 100 t.Run("bearer-token-file is empty", func(t *testing.T) { 101 defer os.Remove(tokenSinkFile) 102 103 require.NoError(t, ioutil.WriteFile(bearerTokenFile, []byte(""), 0600)) 104 105 ui := cli.NewMockUi() 106 cmd := New(ui) 107 108 args := []string{ 109 "-http-addr=" + a.HTTPAddr(), 110 "-token=root", 111 "-method=test", 112 "-token-sink-file", tokenSinkFile, 113 "-bearer-token-file", bearerTokenFile, 114 } 115 116 code := cmd.Run(args) 117 require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) 118 require.Contains(t, ui.ErrorWriter.String(), "No bearer token found in") 119 }) 120 121 require.NoError(t, ioutil.WriteFile(bearerTokenFile, []byte("demo-token"), 0600)) 122 123 t.Run("try login with no method configured", func(t *testing.T) { 124 defer os.Remove(tokenSinkFile) 125 126 ui := cli.NewMockUi() 127 cmd := New(ui) 128 129 args := []string{ 130 "-http-addr=" + a.HTTPAddr(), 131 "-token=root", 132 "-method=test", 133 "-token-sink-file", tokenSinkFile, 134 "-bearer-token-file", bearerTokenFile, 135 } 136 137 code := cmd.Run(args) 138 require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) 139 require.Contains(t, ui.ErrorWriter.String(), "403 (ACL not found)") 140 }) 141 142 testSessionID := testauth.StartSession() 143 defer testauth.ResetSession(testSessionID) 144 145 testauth.InstallSessionToken( 146 testSessionID, 147 "demo-token", 148 "default", "demo", "76091af4-4b56-11e9-ac4b-708b11801cbe", 149 ) 150 151 { 152 _, _, err := client.ACL().AuthMethodCreate( 153 &api.ACLAuthMethod{ 154 Name: "test", 155 Type: "testing", 156 Config: map[string]interface{}{ 157 "SessionID": testSessionID, 158 }, 159 }, 160 &api.WriteOptions{Token: "root"}, 161 ) 162 require.NoError(t, err) 163 } 164 165 t.Run("try login with method configured but no binding rules", func(t *testing.T) { 166 defer os.Remove(tokenSinkFile) 167 168 ui := cli.NewMockUi() 169 cmd := New(ui) 170 171 args := []string{ 172 "-http-addr=" + a.HTTPAddr(), 173 "-token=root", 174 "-method=test", 175 "-token-sink-file", tokenSinkFile, 176 "-bearer-token-file", bearerTokenFile, 177 } 178 179 code := cmd.Run(args) 180 require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) 181 require.Contains(t, ui.ErrorWriter.String(), "403 (Permission denied)") 182 }) 183 184 { 185 _, _, err := client.ACL().BindingRuleCreate(&api.ACLBindingRule{ 186 AuthMethod: "test", 187 BindType: api.BindingRuleBindTypeService, 188 BindName: "${serviceaccount.name}", 189 Selector: "serviceaccount.namespace==default", 190 }, 191 &api.WriteOptions{Token: "root"}, 192 ) 193 require.NoError(t, err) 194 } 195 196 t.Run("try login with method configured and binding rules", func(t *testing.T) { 197 defer os.Remove(tokenSinkFile) 198 199 ui := cli.NewMockUi() 200 cmd := New(ui) 201 202 args := []string{ 203 "-http-addr=" + a.HTTPAddr(), 204 "-token=root", 205 "-method=test", 206 "-token-sink-file", tokenSinkFile, 207 "-bearer-token-file", bearerTokenFile, 208 } 209 210 code := cmd.Run(args) 211 require.Equal(t, 0, code, "err: %s", ui.ErrorWriter.String()) 212 require.Empty(t, ui.ErrorWriter.String()) 213 require.Empty(t, ui.OutputWriter.String()) 214 215 raw, err := ioutil.ReadFile(tokenSinkFile) 216 require.NoError(t, err) 217 218 token := strings.TrimSpace(string(raw)) 219 require.Len(t, token, 36, "must be a valid uid: %s", token) 220 }) 221} 222 223func TestLoginCommand_k8s(t *testing.T) { 224 t.Parallel() 225 226 testDir := testutil.TempDir(t, "acl") 227 defer os.RemoveAll(testDir) 228 229 a := agent.NewTestAgent(t, t.Name(), ` 230 primary_datacenter = "dc1" 231 acl { 232 enabled = true 233 tokens { 234 master = "root" 235 } 236 }`) 237 238 defer a.Shutdown() 239 testrpc.WaitForLeader(t, a.RPC, "dc1") 240 241 client := a.Client() 242 243 tokenSinkFile := filepath.Join(testDir, "test.token") 244 bearerTokenFile := filepath.Join(testDir, "bearer.token") 245 246 // the "B" jwt will be the one being reviewed 247 require.NoError(t, ioutil.WriteFile(bearerTokenFile, []byte(acl.TestKubernetesJWT_B), 0600)) 248 249 // spin up a fake api server 250 testSrv := kubeauth.StartTestAPIServer(t) 251 defer testSrv.Stop() 252 253 testSrv.AuthorizeJWT(acl.TestKubernetesJWT_A) 254 testSrv.SetAllowedServiceAccount( 255 "default", 256 "demo", 257 "76091af4-4b56-11e9-ac4b-708b11801cbe", 258 "", 259 acl.TestKubernetesJWT_B, 260 ) 261 262 { 263 _, _, err := client.ACL().AuthMethodCreate( 264 &api.ACLAuthMethod{ 265 Name: "k8s", 266 Type: "kubernetes", 267 Config: map[string]interface{}{ 268 "Host": testSrv.Addr(), 269 "CACert": testSrv.CACert(), 270 // the "A" jwt will be the one with token review privs 271 "ServiceAccountJWT": acl.TestKubernetesJWT_A, 272 }, 273 }, 274 &api.WriteOptions{Token: "root"}, 275 ) 276 require.NoError(t, err) 277 } 278 279 { 280 _, _, err := client.ACL().BindingRuleCreate(&api.ACLBindingRule{ 281 AuthMethod: "k8s", 282 BindType: api.BindingRuleBindTypeService, 283 BindName: "${serviceaccount.name}", 284 Selector: "serviceaccount.namespace==default", 285 }, 286 &api.WriteOptions{Token: "root"}, 287 ) 288 require.NoError(t, err) 289 } 290 291 t.Run("try login with method configured and binding rules", func(t *testing.T) { 292 defer os.Remove(tokenSinkFile) 293 294 ui := cli.NewMockUi() 295 cmd := New(ui) 296 297 args := []string{ 298 "-http-addr=" + a.HTTPAddr(), 299 "-token=root", 300 "-method=k8s", 301 "-token-sink-file", tokenSinkFile, 302 "-bearer-token-file", bearerTokenFile, 303 } 304 305 code := cmd.Run(args) 306 require.Equal(t, 0, code, "err: %s", ui.ErrorWriter.String()) 307 require.Empty(t, ui.ErrorWriter.String()) 308 require.Empty(t, ui.OutputWriter.String()) 309 310 raw, err := ioutil.ReadFile(tokenSinkFile) 311 require.NoError(t, err) 312 313 token := strings.TrimSpace(string(raw)) 314 require.Len(t, token, 36, "must be a valid uid: %s", token) 315 }) 316} 317