// Read is used to perform a read-only transaction that doesn't modify the state // store. This is much more scaleable since it doesn't go through Raft and // supports staleness, so this should be preferred if you're just performing // reads. func (t *Txn) Read(args *structs.TxnReadRequest, reply *structs.TxnReadResponse) error { if done, err := t.srv.forward("Txn.Read", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"consul", "txn", "read"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. t.srv.setQueryMeta(&reply.QueryMeta) if args.RequireConsistent { if err := t.srv.consistentRead(); err != nil { return err } } // Run the pre-checks before we perform the read. acl, err := t.srv.resolveToken(args.Token) if err != nil { return err } reply.Errors = t.preCheck(acl, args.Ops) if len(reply.Errors) > 0 { return nil } // Run the read transaction. state := t.srv.fsm.State() reply.Results, reply.Errors = state.TxnRO(args.Ops) if acl != nil { reply.Results = FilterTxnResults(acl, reply.Results) } return nil }
func TestTxn_Read_ACLDeny(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" c.ACLMasterToken = "root" c.ACLDefaultPolicy = "deny" }) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") // Put in a key to read back. state := s1.fsm.State() d := &structs.DirEntry{ Key: "nope", Value: []byte("hello"), } if err := state.KVSSet(1, d); err != nil { t.Fatalf("err: %v", err) } // Create the ACL. var id string { arg := structs.ACLRequest{ Datacenter: "dc1", Op: structs.ACLSet, ACL: structs.ACL{ Name: "User token", Type: structs.ACLTypeClient, Rules: testListRules, }, WriteRequest: structs.WriteRequest{Token: "root"}, } if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &id); err != nil { t.Fatalf("err: %v", err) } } // Set up a transaction where every operation should get blocked due to // ACLs. arg := structs.TxnReadRequest{ Datacenter: "dc1", Ops: structs.TxnOps{ &structs.TxnOp{ KV: &structs.TxnKVOp{ Verb: structs.KVSGet, DirEnt: structs.DirEntry{ Key: "nope", }, }, }, &structs.TxnOp{ KV: &structs.TxnKVOp{ Verb: structs.KVSGetTree, DirEnt: structs.DirEntry{ Key: "nope", }, }, }, &structs.TxnOp{ KV: &structs.TxnKVOp{ Verb: structs.KVSCheckSession, DirEnt: structs.DirEntry{ Key: "nope", }, }, }, &structs.TxnOp{ KV: &structs.TxnKVOp{ Verb: structs.KVSCheckIndex, DirEnt: structs.DirEntry{ Key: "nope", }, }, }, }, QueryOptions: structs.QueryOptions{ Token: id, }, } var out structs.TxnReadResponse if err := msgpackrpc.CallWithCodec(codec, "Txn.Read", &arg, &out); err != nil { t.Fatalf("err: %v", err) } // Verify the transaction's return value. expected := structs.TxnReadResponse{ QueryMeta: structs.QueryMeta{ KnownLeader: true, }, } for i, op := range arg.Ops { switch op.KV.Verb { case structs.KVSGet, structs.KVSGetTree: // These get filtered but won't result in an error. default: expected.Errors = append(expected.Errors, &structs.TxnError{i, permissionDeniedErr.Error()}) } } if !reflect.DeepEqual(out, expected) { t.Fatalf("bad %v", out) } }