brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aleds...@apache.org
Subject [1/2] incubator-brooklyn git commit: Adds CreateUserPolicy
Date Wed, 15 Apr 2015 05:51:37 GMT
Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 4f6fa6ca9 -> 10853fc04


Adds CreateUserPolicy


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/bea997bc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/bea997bc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/bea997bc

Branch: refs/heads/master
Commit: bea997bc762cc55d1c188245ccb98a53606db883
Parents: 4f6fa6c
Author: Aled Sage <aled.sage@gmail.com>
Authored: Tue Apr 14 14:48:53 2015 -0500
Committer: Aled Sage <aled.sage@gmail.com>
Committed: Wed Apr 15 00:40:45 2015 -0500

----------------------------------------------------------------------
 .../brooklyn/policy/os/CreateUserPolicy.java    | 174 +++++++++++++++++++
 .../policy/os/CreateUserPolicyLiveTest.java     | 113 ++++++++++++
 .../policy/os/CreateUserPolicyTest.java         | 138 +++++++++++++++
 3 files changed, 425 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bea997bc/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java b/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
new file mode 100644
index 0000000..80851da
--- /dev/null
+++ b/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 brooklyn.policy.os;
+
+import java.util.List;
+
+import org.jclouds.compute.config.AdminAccessConfiguration;
+import org.jclouds.scriptbuilder.functions.InitAdminAccess;
+import org.jclouds.scriptbuilder.statements.login.AdminAccess;
+import org.jclouds.scriptbuilder.statements.ssh.SshdConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.event.basic.Sensors;
+import brooklyn.location.Location;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * When attached to an entity, this will monitor for when an {@link SshMachineLocation} is
added to that entity
+ * (e.g. when a VM has been provisioned for it).
+ * 
+ * The policy will then (asynchronously) add a new user to the VM, with a randomly generated
password.
+ * The ssh details will be set as a sensor on the entity.
+ * 
+ * If this is used, it is strongly encouraged to tell users to change the password on first
login.
+ * 
+ * A preferred mechanism would be for an external key-management tool to generate ssh key-pairs
for
+ * the user, and for the public key to be passed to Brooklyn. However, there is not a customer

+ * requirement for that yet, so focusing on the password-approach.
+ */
+@Beta
+public class CreateUserPolicy extends AbstractPolicy implements SensorEventListener<Location>
{
+
+    // TODO Should add support for authorizing ssh keys as well
+    
+    // TODO Should review duplication with:
+    //  - JcloudsLocationConfig.GRANT_USER_SUDO
+    //    (but config default/description and context of use are different)
+    //  - AdminAccess in JcloudsLocation.createUserStatements
+    
+    // TODO Could make the password explicitly configurable, or auto-generate if not set?
+    
+    private static final Logger LOG = LoggerFactory.getLogger(CreateUserPolicy.class);
+
+    @SetFromFlag("user")
+    public static final ConfigKey<String> VM_USERNAME = ConfigKeys.newStringConfigKey("createuser.vm.user.name");
+
+    @SetFromFlag("grantSudo")
+    public static final ConfigKey<Boolean> GRANT_SUDO = ConfigKeys.newBooleanConfigKey(
+            "createuser.vm.user.grantSudo",
+            "Whether to give the new user sudo rights",
+            false);
+
+    public static final AttributeSensor<String> VM_USER_CREDENTIALS = Sensors.newStringSensor(
+            "createuser.vm.user.credentials",
+            "The \"<user> : <password> @ <hostname>:<port>\"");
+
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        subscribe(entity, AbstractEntity.LOCATION_ADDED, this);
+    }
+
+    @Override
+    public void onEvent(SensorEvent<Location> event) {
+        final Entity entity = event.getSource();
+        final Location loc = event.getValue();
+        if (loc instanceof SshMachineLocation) {
+            addUserAsync(entity, (SshMachineLocation)loc);
+        }
+    }
+
+    protected void addUserAsync(final Entity entity, final SshMachineLocation machine) {
+        ((EntityInternal)entity).getExecutionContext().execute(new Runnable() {
+            public void run() {
+                addUser(entity, machine);
+            }});
+    }
+    
+    protected void addUser(Entity entity, SshMachineLocation machine) {
+        boolean grantSudo = getRequiredConfig(GRANT_SUDO);
+        String user = getRequiredConfig(VM_USERNAME);
+        String password = Identifiers.makeRandomId(12);
+        String hostname = machine.getAddress().getHostName();
+        int port = machine.getPort();
+        String creds = user + " : " + password + " @ " +hostname + ":" + port;
+        
+        LOG.info("Adding auto-generated user "+user+" @ "+hostname+":"+port);
+        
+        // Build the command to create the user
+        // Note AdminAccess requires _all_ fields set, due to http://code.google.com/p/jclouds/issues/detail?id=1095
+        // If jclouds grants Sudo rights, it overwrites the /etc/sudoers, which makes integration
tests very dangerous! Not using it.
+        AdminAccess adminAccess = AdminAccess.builder()
+                .adminUsername(user)
+                .adminPassword(password)
+                .grantSudoToAdminUser(false)
+                .resetLoginPassword(true)
+                .loginPassword(password)
+                .authorizeAdminPublicKey(false)
+                .adminPublicKey("ignored")
+                .installAdminPrivateKey(false)
+                .adminPrivateKey("ignore")
+                .lockSsh(false)
+                .build();
+        
+        org.jclouds.scriptbuilder.domain.OsFamily scriptOsFamily = (machine.getMachineDetails().getOsDetails().isWindows())

+                ? org.jclouds.scriptbuilder.domain.OsFamily.WINDOWS
+                : org.jclouds.scriptbuilder.domain.OsFamily.UNIX;
+        
+        InitAdminAccess initAdminAccess = new InitAdminAccess(new AdminAccessConfiguration.Default());
+        initAdminAccess.visit(adminAccess);
+        String cmd = adminAccess.render(scriptOsFamily);
+
+        // Exec command to create the user
+        int result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(),
true), "create-user-"+user, ImmutableList.of(cmd));
+        if (result != 0) {
+            throw new IllegalStateException("Failed to auto-generate user, using command
"+cmd);
+        }
+
+        // Exec command to grant password-access to sshd (which may have been disabled earlier).
+        cmd = new SshdConfig(ImmutableMap.of("PasswordAuthentication", "yes")).render(scriptOsFamily);
+        result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(), true),
"create-user-"+user, ImmutableList.of(cmd));
+        if (result != 0) {
+            throw new IllegalStateException("Failed to enable ssh-login-with-password, using
command "+cmd);
+        }
+
+        // Exec command to grant sudo rights.
+        if (grantSudo) {
+            List<String> cmds = ImmutableList.of(
+                    "cat >> /etc/sudoers <<-'END_OF_JCLOUDS_FILE'\n"+
+                            user+" ALL = (ALL) NOPASSWD:ALL\n"+
+                            "END_OF_JCLOUDS_FILE\n",
+                    "chmod 0440 /etc/sudoers");
+            result = machine.execScript(ImmutableMap.of(SshTool.PROP_RUN_AS_ROOT.getName(),
true), "add-user-to-sudoers-"+user, cmds);
+            if (result != 0) {
+                throw new IllegalStateException("Failed to auto-generate user, using command
"+cmd);
+            }
+        }
+        
+        ((EntityLocal)entity).setAttribute(VM_USER_CREDENTIALS, creds);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bea997bc/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyLiveTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyLiveTest.java
b/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyLiveTest.java
new file mode 100644
index 0000000..2913096
--- /dev/null
+++ b/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyLiveTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 brooklyn.policy.os;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.MachineProvisioningLocation;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.policy.PolicySpec;
+import brooklyn.policy.os.CreateUserPolicy;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.internal.ssh.SshTool;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class CreateUserPolicyLiveTest extends BrooklynAppLiveTestSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CreateUserPolicyLiveTest.class);
+
+    // TODO Fails on OS X
+    @Test(groups={"Integration", "WIP"})
+    public void testIntegrationCreatesUser() throws Exception {
+        LocalhostMachineProvisioningLocation loc = app.newLocalhostProvisioningLocation();
+        runTestCreatesUser(loc);
+    }
+    
+    @Test(groups="Live")
+    @SuppressWarnings("unchecked")
+    public void testLiveCreatesUser() throws Exception {
+        String locSpec = "jclouds:softlayer:ams01";
+        ImmutableMap<String, String> locArgs = ImmutableMap.of("imageId", "CENTOS_6_64");
+        Location loc = mgmt.getLocationRegistry().resolve(locSpec, locArgs);
+        runTestCreatesUser((MachineProvisioningLocation<SshMachineLocation>) loc);
+    }
+    
+    public void runTestCreatesUser(MachineProvisioningLocation<SshMachineLocation>
loc) throws Exception {
+        String newUsername = Identifiers.makeRandomId(16);
+        SshMachineLocation machine = loc.obtain(ImmutableMap.of());
+        
+        try {
+            app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                    .policy(PolicySpec.create(CreateUserPolicy.class)
+                            .configure(CreateUserPolicy.GRANT_SUDO, true)
+                            .configure(CreateUserPolicy.VM_USERNAME, newUsername)));
+            TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+
+            app.start(ImmutableList.of(machine));
+            
+            String creds = EntityTestUtils.assertAttributeEventuallyNonNull(entity, CreateUserPolicy.VM_USER_CREDENTIALS);
+            Pattern pattern = Pattern.compile("(.*) : (.*) @ (.*):(.*)");
+            Matcher matcher = pattern.matcher(creds);
+            assertTrue(matcher.matches());
+            String username = matcher.group(1).trim();
+            String password = matcher.group(2).trim();
+            String hostname = matcher.group(3).trim();
+            String port = matcher.group(4).trim();
+            
+            assertEquals(newUsername, username);
+            assertEquals(hostname, machine.getAddress().getHostName());
+            assertEquals(port, ""+machine.getPort());
+    
+            SshMachineLocation machine2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                    .configure(SshTool.PROP_USER, newUsername)
+                    .configure(SshMachineLocation.PASSWORD, password)
+                    .configure("address", hostname)
+                    .configure(SshMachineLocation.SSH_PORT, Integer.parseInt(port)));
+            
+            LOG.info("Checking ssh'able for auto-generated user: machine="+machine+"; creds="+creds);
+            assertTrue(machine2.isSshable(), "machine="+machine+"; creds="+creds);
+            
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            LOG.info("Deleting auto-generated user "+newUsername);
+            machine.execScript("delete-user-"+newUsername, ImmutableList.of("sudo userdel
-f "+newUsername));
+            
+            loc.release(machine);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bea997bc/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyTest.java
b/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyTest.java
new file mode 100644
index 0000000..855d994
--- /dev/null
+++ b/locations/jclouds/src/test/java/brooklyn/policy/os/CreateUserPolicyTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 brooklyn.policy.os;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.policy.PolicySpec;
+import brooklyn.policy.os.CreateUserPolicy;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.internal.ssh.SshTool;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class CreateUserPolicyTest extends BrooklynAppUnitTestSupport {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(CreateUserPolicyTest.class);
+
+    public static class RecordingSshMachineLocation extends SshMachineLocation {
+        private static final long serialVersionUID = 1641930081769106380L;
+        
+        public static List<List<String>> execScriptCalls = Lists.newArrayList();
+
+        @Override 
+        public int execScript(String summary, List<String> cmds) {
+            execScriptCalls.add(cmds);
+            return 0;
+        }
+        @Override 
+        public int execScript(Map<String,?> props, String summaryForLogging, List<String>
cmds) {
+            execScriptCalls.add(cmds);
+            return 0;
+        }
+        @Override 
+        public int execScript(String summaryForLogging, List<String> cmds, Map<String,?>
env) {
+            execScriptCalls.add(cmds);
+            return 0;
+        }
+        @Override 
+        public int execScript(Map<String,?> props, String summaryForLogging, List<String>
cmds, Map<String,?> env) {
+            execScriptCalls.add(cmds);
+            return 0;
+        }
+    }
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        RecordingSshMachineLocation.execScriptCalls.clear();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            super.tearDown();
+        } finally {
+            RecordingSshMachineLocation.execScriptCalls.clear();
+        }
+    }
+    
+    @Test
+    public void testCallsCreateUser() throws Exception {
+        SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(RecordingSshMachineLocation.class)
+                .configure(SshTool.PROP_USER, "myuser")
+                .configure(SshTool.PROP_PASSWORD, "mypassword")
+                .configure("address", "1.2.3.4")
+                .configure(SshTool.PROP_PORT, 1234));
+    
+        String newUsername = "mynewuser";
+        
+        app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .policy(PolicySpec.create(CreateUserPolicy.class)
+                        .configure(CreateUserPolicy.GRANT_SUDO, true)
+                        .configure(CreateUserPolicy.VM_USERNAME, newUsername)));
+        TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+        app.start(ImmutableList.of(machine));
+        
+        String creds = EntityTestUtils.assertAttributeEventuallyNonNull(entity, CreateUserPolicy.VM_USER_CREDENTIALS);
+        Pattern pattern = Pattern.compile("(.*) : (.*) @ (.*):(.*)");
+        Matcher matcher = pattern.matcher(creds);
+        assertTrue(matcher.matches());
+        String username2 = matcher.group(1).trim();
+        String password = matcher.group(2).trim();
+        String hostname = matcher.group(3).trim();
+        String port = matcher.group(4).trim();
+        
+        assertEquals(newUsername, username2);
+        assertEquals(hostname, "1.2.3.4");
+        assertEquals(password.length(), 12);
+        assertEquals(port, "1234");
+
+        boolean found = false;
+        for (List<String> cmds : RecordingSshMachineLocation.execScriptCalls) {
+            if (cmds.toString().contains("useradd")) {
+                found = true;
+                break;
+            }
+        }
+        assertTrue(found, "useradd not found in: "+RecordingSshMachineLocation.execScriptCalls);
+    }
+}


Mime
View raw message