hub.fastgit.org/hashicorp/consul.git@v1.4.5/agent/consul/session_endpoint.go (about) 1 package consul 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/armon/go-metrics" 8 "github.com/hashicorp/consul/acl" 9 "github.com/hashicorp/consul/agent/consul/state" 10 "github.com/hashicorp/consul/agent/structs" 11 "github.com/hashicorp/go-memdb" 12 "github.com/hashicorp/go-uuid" 13 ) 14 15 // Session endpoint is used to manipulate sessions for KV 16 type Session struct { 17 srv *Server 18 } 19 20 // Apply is used to apply a modifying request to the data store. This should 21 // only be used for operations that modify the data 22 func (s *Session) Apply(args *structs.SessionRequest, reply *string) error { 23 if done, err := s.srv.forward("Session.Apply", args, args, reply); done { 24 return err 25 } 26 defer metrics.MeasureSince([]string{"session", "apply"}, time.Now()) 27 28 // Verify the args 29 if args.Session.ID == "" && args.Op == structs.SessionDestroy { 30 return fmt.Errorf("Must provide ID") 31 } 32 if args.Session.Node == "" && args.Op == structs.SessionCreate { 33 return fmt.Errorf("Must provide Node") 34 } 35 36 // Fetch the ACL token, if any, and apply the policy. 37 rule, err := s.srv.ResolveToken(args.Token) 38 if err != nil { 39 return err 40 } 41 if rule != nil && s.srv.config.ACLEnforceVersion8 { 42 switch args.Op { 43 case structs.SessionDestroy: 44 state := s.srv.fsm.State() 45 _, existing, err := state.SessionGet(nil, args.Session.ID) 46 if err != nil { 47 return fmt.Errorf("Session lookup failed: %v", err) 48 } 49 if existing == nil { 50 return fmt.Errorf("Unknown session %q", args.Session.ID) 51 } 52 if !rule.SessionWrite(existing.Node) { 53 return acl.ErrPermissionDenied 54 } 55 56 case structs.SessionCreate: 57 if !rule.SessionWrite(args.Session.Node) { 58 return acl.ErrPermissionDenied 59 } 60 61 default: 62 return fmt.Errorf("Invalid session operation %q", args.Op) 63 } 64 } 65 66 // Ensure that the specified behavior is allowed 67 switch args.Session.Behavior { 68 case "": 69 // Default behavior to Release for backwards compatibility 70 args.Session.Behavior = structs.SessionKeysRelease 71 case structs.SessionKeysRelease: 72 case structs.SessionKeysDelete: 73 default: 74 return fmt.Errorf("Invalid Behavior setting '%s'", args.Session.Behavior) 75 } 76 77 // Ensure the Session TTL is valid if provided 78 if args.Session.TTL != "" { 79 ttl, err := time.ParseDuration(args.Session.TTL) 80 if err != nil { 81 return fmt.Errorf("Session TTL '%s' invalid: %v", args.Session.TTL, err) 82 } 83 84 if ttl != 0 && (ttl < s.srv.config.SessionTTLMin || ttl > structs.SessionTTLMax) { 85 return fmt.Errorf("Invalid Session TTL '%d', must be between [%v=%v]", 86 ttl, s.srv.config.SessionTTLMin, structs.SessionTTLMax) 87 } 88 } 89 90 // If this is a create, we must generate the Session ID. This must 91 // be done prior to appending to the raft log, because the ID is not 92 // deterministic. Once the entry is in the log, the state update MUST 93 // be deterministic or the followers will not converge. 94 if args.Op == structs.SessionCreate { 95 // Generate a new session ID, verify uniqueness 96 state := s.srv.fsm.State() 97 for { 98 var err error 99 if args.Session.ID, err = uuid.GenerateUUID(); err != nil { 100 s.srv.logger.Printf("[ERR] consul.session: UUID generation failed: %v", err) 101 return err 102 } 103 _, sess, err := state.SessionGet(nil, args.Session.ID) 104 if err != nil { 105 s.srv.logger.Printf("[ERR] consul.session: Session lookup failed: %v", err) 106 return err 107 } 108 if sess == nil { 109 break 110 } 111 } 112 } 113 114 // Apply the update 115 resp, err := s.srv.raftApply(structs.SessionRequestType, args) 116 if err != nil { 117 s.srv.logger.Printf("[ERR] consul.session: Apply failed: %v", err) 118 return err 119 } 120 121 if args.Op == structs.SessionCreate && args.Session.TTL != "" { 122 // If we created a session with a TTL, reset the expiration timer 123 s.srv.resetSessionTimer(args.Session.ID, &args.Session) 124 } else if args.Op == structs.SessionDestroy { 125 // If we destroyed a session, it might potentially have a TTL, 126 // and we need to clear the timer 127 s.srv.clearSessionTimer(args.Session.ID) 128 } 129 130 if respErr, ok := resp.(error); ok { 131 return respErr 132 } 133 134 // Check if the return type is a string 135 if respString, ok := resp.(string); ok { 136 *reply = respString 137 } 138 return nil 139 } 140 141 // Get is used to retrieve a single session 142 func (s *Session) Get(args *structs.SessionSpecificRequest, 143 reply *structs.IndexedSessions) error { 144 if done, err := s.srv.forward("Session.Get", args, args, reply); done { 145 return err 146 } 147 148 return s.srv.blockingQuery( 149 &args.QueryOptions, 150 &reply.QueryMeta, 151 func(ws memdb.WatchSet, state *state.Store) error { 152 index, session, err := state.SessionGet(ws, args.Session) 153 if err != nil { 154 return err 155 } 156 157 reply.Index = index 158 if session != nil { 159 reply.Sessions = structs.Sessions{session} 160 } else { 161 reply.Sessions = nil 162 } 163 if err := s.srv.filterACL(args.Token, reply); err != nil { 164 return err 165 } 166 return nil 167 }) 168 } 169 170 // List is used to list all the active sessions 171 func (s *Session) List(args *structs.DCSpecificRequest, 172 reply *structs.IndexedSessions) error { 173 if done, err := s.srv.forward("Session.List", args, args, reply); done { 174 return err 175 } 176 177 return s.srv.blockingQuery( 178 &args.QueryOptions, 179 &reply.QueryMeta, 180 func(ws memdb.WatchSet, state *state.Store) error { 181 index, sessions, err := state.SessionList(ws) 182 if err != nil { 183 return err 184 } 185 186 reply.Index, reply.Sessions = index, sessions 187 if err := s.srv.filterACL(args.Token, reply); err != nil { 188 return err 189 } 190 return nil 191 }) 192 } 193 194 // NodeSessions is used to get all the sessions for a particular node 195 func (s *Session) NodeSessions(args *structs.NodeSpecificRequest, 196 reply *structs.IndexedSessions) error { 197 if done, err := s.srv.forward("Session.NodeSessions", args, args, reply); done { 198 return err 199 } 200 201 return s.srv.blockingQuery( 202 &args.QueryOptions, 203 &reply.QueryMeta, 204 func(ws memdb.WatchSet, state *state.Store) error { 205 index, sessions, err := state.NodeSessions(ws, args.Node) 206 if err != nil { 207 return err 208 } 209 210 reply.Index, reply.Sessions = index, sessions 211 if err := s.srv.filterACL(args.Token, reply); err != nil { 212 return err 213 } 214 return nil 215 }) 216 } 217 218 // Renew is used to renew the TTL on a single session 219 func (s *Session) Renew(args *structs.SessionSpecificRequest, 220 reply *structs.IndexedSessions) error { 221 if done, err := s.srv.forward("Session.Renew", args, args, reply); done { 222 return err 223 } 224 defer metrics.MeasureSince([]string{"session", "renew"}, time.Now()) 225 226 // Get the session, from local state. 227 state := s.srv.fsm.State() 228 index, session, err := state.SessionGet(nil, args.Session) 229 if err != nil { 230 return err 231 } 232 233 reply.Index = index 234 if session == nil { 235 return nil 236 } 237 238 // Fetch the ACL token, if any, and apply the policy. 239 rule, err := s.srv.ResolveToken(args.Token) 240 if err != nil { 241 return err 242 } 243 if rule != nil && s.srv.config.ACLEnforceVersion8 { 244 if !rule.SessionWrite(session.Node) { 245 return acl.ErrPermissionDenied 246 } 247 } 248 249 // Reset the session TTL timer. 250 reply.Sessions = structs.Sessions{session} 251 if err := s.srv.resetSessionTimer(args.Session, session); err != nil { 252 s.srv.logger.Printf("[ERR] consul.session: Session renew failed: %v", err) 253 return err 254 } 255 256 return nil 257 }