/*- * -\-\- * Helios Services * -- * Copyright (C) 2016 Spotify AB * -- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * -/-/- */ package com.spotify.helios.servicescommon; import static org.apache.zookeeper.ZooDefs.Perms.CREATE; import static org.apache.zookeeper.ZooDefs.Perms.DELETE; import static org.apache.zookeeper.ZooDefs.Perms.READ; import static org.apache.zookeeper.ZooDefs.Perms.WRITE; import com.spotify.helios.common.Hash; import com.spotify.helios.servicescommon.coordination.Paths; import org.apache.curator.framework.api.ACLProvider; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.bouncycastle.util.encoders.Base64; /** * Contains methods for creating ACLProvider used by Helios (agents and masters), * that controls the ACLs set on nodes when they are created. * * <p>If no authentication is provided a client has no access (not even read access). There are two * different roles: agents and masters. Masters are granted all permissions except ADMIN to all * nodes. Agents are only granted the permissions it needs, to a subset of all the nodes. This * limits the consequences of the agent credentials being compromised. * * <p>Currently all agents share the same permissions, using a single shared credential. I.e. an * agent can modify data that "belongs" to another agent (to the same extent that it can modify data * that belongs to it). * * <p>The provider uses ZooKeeper's "digest" ACL scheme. */ public class ZooKeeperAclProviders { private static final String PATH_COMPONENT_WILDCARD = "[^/]+"; private static final String DIGEST_SCHEME = "digest"; public static String digest(final String user, final String password) { return Base64.toBase64String(Hash.sha1digest( String.format("%s:%s", user, password).getBytes())); } public static ACLProvider heliosAclProvider(final String masterUser, final String masterDigest, final String agentUser, final String agentDigest) { final Id masterId = new Id(DIGEST_SCHEME, String.format("%s:%s", masterUser, masterDigest)); final Id agentId = new Id(DIGEST_SCHEME, String.format("%s:%s", agentUser, agentDigest)); return RuleBasedZooKeeperAclProvider.builder() // Set the default ACL to grant everyone CRWD permissions to master and READ to agents. // Note that the default ACLs should never be used though, as we set up catch-all rules // below. .defaultAcl(new ACL(CREATE | READ | WRITE | DELETE, masterId), new ACL(READ, agentId)) // Master has CRWD permissions on all paths .rule(".*", CREATE | READ | WRITE | DELETE, masterId) // Agent has READ permission on all paths .rule(".*", READ, agentId) // The agent needs to create the /config/hosts/<host> path and nodes below it. It also needs // DELETE permissions since agents will delete it's "own" subtree when re-registering itself // (when a machine is reinstalled with the same name). Note that agent does however not have // any permissions, except read, to /config/hosts/<host>/jobs -- which means an agent can't // [typically] cause jobs to be deployed on other agents (this is not fool-proof -- if you // can predict an agent's name before it's created you inject data that causes it). .rule(Paths.configHosts(), CREATE | DELETE, agentId) .rule(Paths.configHost(PATH_COMPONENT_WILDCARD), CREATE | DELETE, agentId) .rule(Paths.configHostId(PATH_COMPONENT_WILDCARD), CREATE | DELETE, agentId) .rule(Paths.configHostPorts(PATH_COMPONENT_WILDCARD), CREATE | DELETE, agentId) .rule(Paths.statusHosts(), CREATE | DELETE, agentId) .rule(Paths.statusHost(PATH_COMPONENT_WILDCARD), CREATE | DELETE, agentId) .rule(Paths.statusHostJobs(PATH_COMPONENT_WILDCARD), CREATE | DELETE, agentId) .rule(Paths.statusHostJob(PATH_COMPONENT_WILDCARD, PATH_COMPONENT_WILDCARD), WRITE, agentId) .rule(Paths.statusHostAgentInfo(PATH_COMPONENT_WILDCARD), WRITE, agentId) .rule(Paths.statusHostInfo(PATH_COMPONENT_WILDCARD), WRITE, agentId) .rule(Paths.statusHostLabels(PATH_COMPONENT_WILDCARD), WRITE, agentId) .rule(Paths.statusHostEnvVars(PATH_COMPONENT_WILDCARD), WRITE, agentId) .rule(Paths.statusHostUp(PATH_COMPONENT_WILDCARD), WRITE, agentId) // Grant agents CREATE permissions to the entire /history/jobs tree .rule(Paths.historyJobs() + "(/.+)?", CREATE, agentId) // Grant agents DELETE permissions to nodes under /history/jobs/<job>/hosts/<host>/events // -- needed for pruning old task history events .rule(Paths.historyJobHostEvents( PATH_COMPONENT_WILDCARD, PATH_COMPONENT_WILDCARD), DELETE, agentId) .build(); } }