Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 1 | package impl |
| 2 | |
| 3 | import ( |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 4 | "fmt" |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 5 | "os" |
| 6 | "path/filepath" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 7 | "strings" |
Jiri Simsa | 24e87aa | 2014-06-09 09:27:34 -0700 | [diff] [blame] | 8 | |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 9 | vsecurity "veyron/security" |
| 10 | vflag "veyron/security/flag" |
| 11 | "veyron/security/serialization" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 12 | inode "veyron/services/mgmt/node" |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 13 | "veyron/services/mgmt/node/config" |
Jiri Simsa | 24e87aa | 2014-06-09 09:27:34 -0700 | [diff] [blame] | 14 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 15 | "veyron2/ipc" |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 16 | "veyron2/rt" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 17 | "veyron2/security" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 18 | "veyron2/services/mgmt/node" |
| 19 | "veyron2/verror" |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 20 | "veyron2/vlog" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 21 | ) |
| 22 | |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 23 | // internalState wraps state shared between different node manager |
| 24 | // invocations. |
| 25 | type internalState struct { |
| 26 | callback *callbackState |
| 27 | updating *updatingState |
| 28 | } |
| 29 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 30 | // dispatcher holds the state of the node manager dispatcher. |
| 31 | type dispatcher struct { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 32 | auth security.Authorizer |
| 33 | // internal holds the state that persists across RPC method invocations. |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 34 | internal *internalState |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 35 | // config holds the node manager's (immutable) configuration state. |
| 36 | config *config.State |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 37 | } |
| 38 | |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 39 | const ( |
| 40 | appsSuffix = "apps" |
| 41 | nodeSuffix = "nm" |
| 42 | configSuffix = "cfg" |
| 43 | ) |
| 44 | |
| 45 | var ( |
| 46 | errInvalidSuffix = verror.BadArgf("invalid suffix") |
| 47 | errOperationFailed = verror.Internalf("operation failed") |
| 48 | errInProgress = verror.Existsf("operation in progress") |
| 49 | errIncompatibleUpdate = verror.BadArgf("update failed: mismatching app title") |
| 50 | errUpdateNoOp = verror.NotFoundf("no different version available") |
| 51 | errNotExist = verror.NotFoundf("object does not exist") |
| 52 | errInvalidOperation = verror.BadArgf("invalid operation") |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 53 | errInvalidBlessing = verror.BadArgf("invalid claim blessing") |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 54 | ) |
| 55 | |
Jiri Simsa | 24e87aa | 2014-06-09 09:27:34 -0700 | [diff] [blame] | 56 | // NewDispatcher is the node manager dispatcher factory. |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 57 | func NewDispatcher(config *config.State) (*dispatcher, error) { |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 58 | if err := config.Validate(); err != nil { |
| 59 | return nil, fmt.Errorf("Invalid config %v: %v", config, err) |
| 60 | } |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 61 | d := &dispatcher{ |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 62 | internal: &internalState{ |
Bogdan Caprita | 78b6216 | 2014-08-21 15:35:08 -0700 | [diff] [blame] | 63 | callback: newCallbackState(config.Name), |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 64 | updating: newUpdatingState(), |
Jiri Simsa | 70c3205 | 2014-06-18 11:38:21 -0700 | [diff] [blame] | 65 | }, |
Bogdan Caprita | c87a914 | 2014-07-21 10:38:13 -0700 | [diff] [blame] | 66 | config: config, |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 67 | } |
| 68 | // Prefer ACLs in the nodemanager data directory if they exist. |
| 69 | if data, sig, err := d.getACLFiles(os.O_RDONLY); err != nil { |
| 70 | if d.auth = vflag.NewAuthorizerOrDie(); d.auth == nil { |
| 71 | // If there are no specified ACLs we grant nodemanager access to all |
| 72 | // principal until it is claimed. |
| 73 | d.auth = vsecurity.NewACLAuthorizer(vsecurity.OpenACL()) |
| 74 | } |
| 75 | } else { |
| 76 | defer data.Close() |
| 77 | defer sig.Close() |
| 78 | reader, err := serialization.NewVerifyingReader(data, sig, rt.R().Identity().PublicKey()) |
| 79 | if err != nil { |
| 80 | return nil, fmt.Errorf("Failed to read nodemanager ACL file:%v", err) |
| 81 | } |
| 82 | acl, err := vsecurity.LoadACL(reader) |
| 83 | if err != nil { |
| 84 | return nil, fmt.Errorf("Failed to load nodemanager ACL:%v", err) |
| 85 | } |
| 86 | d.auth = vsecurity.NewACLAuthorizer(acl) |
| 87 | } |
| 88 | return d, nil |
| 89 | } |
| 90 | |
| 91 | func (d *dispatcher) getACLFiles(flag int) (aclData *os.File, aclSig *os.File, err error) { |
| 92 | nodedata := filepath.Join(d.config.Root, "node-manager", "node-data") |
| 93 | perm := os.FileMode(0700) |
| 94 | if err = os.MkdirAll(nodedata, perm); err != nil { |
| 95 | return |
| 96 | } |
| 97 | if aclData, err = os.OpenFile(filepath.Join(nodedata, "acl.nodemanager"), flag, perm); err != nil { |
| 98 | return |
| 99 | } |
| 100 | if aclSig, err = os.OpenFile(filepath.Join(nodedata, "acl.signature"), flag, perm); err != nil { |
| 101 | return |
| 102 | } |
| 103 | return |
| 104 | } |
| 105 | |
| 106 | func (d *dispatcher) claimNodeManager(id security.PublicID) error { |
| 107 | // TODO(gauthamt): Should we start trusting these identity providers? |
| 108 | if id.Names() == nil { |
| 109 | vlog.Errorf("Identity provider for device claimer is not trusted") |
| 110 | return errOperationFailed |
| 111 | } |
| 112 | rt.R().PublicIDStore().Add(id, security.AllPrincipals) |
| 113 | // Create ACLs to transfer nodemanager permissions to the provided identity. |
| 114 | acl := security.ACL{In: make(map[security.BlessingPattern]security.LabelSet)} |
| 115 | for _, name := range id.Names() { |
| 116 | acl.In[security.BlessingPattern(name)] = security.AllLabels |
| 117 | } |
| 118 | d.auth = vsecurity.NewACLAuthorizer(acl) |
| 119 | // Write out the ACLs so that it will persist across restarts. |
| 120 | data, sig, err := d.getACLFiles(os.O_CREATE | os.O_RDWR) |
| 121 | if err != nil { |
| 122 | vlog.Errorf("Failed to create ACL files:%v", err) |
| 123 | return errOperationFailed |
| 124 | } |
| 125 | writer, err := serialization.NewSigningWriteCloser(data, sig, rt.R().Identity(), nil) |
| 126 | if err != nil { |
| 127 | vlog.Errorf("Failed to create NewSigningWriteCloser:%v", err) |
| 128 | return errOperationFailed |
| 129 | } |
| 130 | if err = vsecurity.SaveACL(writer, acl); err != nil { |
| 131 | vlog.Errorf("Failed to SaveACL:%v", err) |
| 132 | return errOperationFailed |
| 133 | } |
| 134 | if err = writer.Close(); err != nil { |
| 135 | vlog.Errorf("Failed to Close() SigningWriteCloser:%v", err) |
| 136 | return errOperationFailed |
| 137 | } |
| 138 | return nil |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | // DISPATCHER INTERFACE IMPLEMENTATION |
| 142 | |
Cosmos Nicolaou | 8bfacf2 | 2014-08-19 11:19:36 -0700 | [diff] [blame] | 143 | func (d *dispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 144 | components := strings.Split(suffix, "/") |
| 145 | for i := 0; i < len(components); i++ { |
| 146 | if len(components[i]) == 0 { |
| 147 | components = append(components[:i], components[i+1:]...) |
| 148 | i-- |
| 149 | } |
| 150 | } |
| 151 | if len(components) == 0 { |
| 152 | return nil, nil, errInvalidSuffix |
| 153 | } |
| 154 | // The implementation of the node manager is split up into several |
| 155 | // invokers, which are instantiated depending on the receiver name |
| 156 | // prefix. |
| 157 | var receiver interface{} |
| 158 | switch components[0] { |
| 159 | case nodeSuffix: |
| 160 | receiver = node.NewServerNode(&nodeInvoker{ |
| 161 | callback: d.internal.callback, |
| 162 | updating: d.internal.updating, |
| 163 | config: d.config, |
Gautham | 82bb995 | 2014-08-28 14:11:51 -0700 | [diff] [blame^] | 164 | disp: d, |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 165 | }) |
| 166 | case appsSuffix: |
| 167 | receiver = node.NewServerApplication(&appInvoker{ |
| 168 | callback: d.internal.callback, |
| 169 | config: d.config, |
| 170 | suffix: components[1:], |
| 171 | }) |
| 172 | case configSuffix: |
| 173 | if len(components) != 2 { |
| 174 | return nil, nil, errInvalidSuffix |
| 175 | } |
| 176 | receiver = inode.NewServerConfig(&configInvoker{ |
| 177 | callback: d.internal.callback, |
| 178 | suffix: components[1], |
| 179 | }) |
| 180 | default: |
| 181 | return nil, nil, errInvalidSuffix |
| 182 | } |
| 183 | return ipc.ReflectInvoker(receiver), d.auth, nil |
Jiri Simsa | 24e87aa | 2014-06-09 09:27:34 -0700 | [diff] [blame] | 184 | } |