| 1 | /** |
| 2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
| 3 | * contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. jclouds licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | package org.jclouds.scriptbuilder.statements.login; |
| 20 | |
| 21 | import static com.google.common.base.Charsets.UTF_8; |
| 22 | import static com.google.common.base.Preconditions.checkNotNull; |
| 23 | |
| 24 | import java.io.File; |
| 25 | import java.io.IOException; |
| 26 | import java.util.Map; |
| 27 | |
| 28 | import org.jclouds.javax.annotation.Nullable; |
| 29 | |
| 30 | import org.jclouds.crypto.Sha512Crypt; |
| 31 | import org.jclouds.domain.Credentials; |
| 32 | import org.jclouds.scriptbuilder.domain.OsFamily; |
| 33 | import org.jclouds.scriptbuilder.domain.Statement; |
| 34 | import org.jclouds.scriptbuilder.domain.StatementList; |
| 35 | import org.jclouds.scriptbuilder.statements.ssh.SshStatements; |
| 36 | |
| 37 | import com.google.common.base.Function; |
| 38 | import com.google.common.base.Supplier; |
| 39 | import com.google.common.base.Throwables; |
| 40 | import com.google.common.collect.ImmutableList; |
| 41 | import com.google.common.collect.ImmutableMap; |
| 42 | import com.google.common.io.Files; |
| 43 | import com.google.inject.ImplementedBy; |
| 44 | |
| 45 | /** |
| 46 | * Controls the administrative access to a node. By default, it will perform the following: |
| 47 | * |
| 48 | * <ul> |
| 49 | * <li>setup a new admin user which folks should use as opposed to the built-in vcloud account</li> |
| 50 | * <ul> |
| 51 | * <li>associate a random password to account</li> |
| 52 | * <ul> |
| 53 | * <li>securely ( use sha 512 on client side and literally rewrite the shadow entry, rather than |
| 54 | * pass password to OS in a script )</li> |
| 55 | * </ul> |
| 56 | * <li>associate the users' ssh public key with the account for login</li> <li> |
| 57 | * associate it with the os group wheel</li> </ul> <li>create os group wheel</li> <li>add sudoers |
| 58 | * for nopassword access to root by group wheel</li> <li>reset root password securely</li> <li> |
| 59 | * lockdown sshd_config for no root login, nor passwords allowed</li> </ul> |
| 60 | * |
| 61 | * @author Adrian Cole |
| 62 | */ |
| 63 | public class AdminAccess implements Statement { |
| 64 | public static AdminAccess.Builder builder() { |
| 65 | return new Builder(); |
| 66 | } |
| 67 | |
| 68 | public static AdminAccess.Builder builder(Function<String, String> cryptFunction) { |
| 69 | return new Builder(cryptFunction); |
| 70 | } |
| 71 | |
| 72 | public static AdminAccess standard() { |
| 73 | return new Builder().build(); |
| 74 | } |
| 75 | |
| 76 | @ImplementedBy(DefaultConfiguration.class) |
| 77 | public static interface Configuration { |
| 78 | Supplier<String> defaultAdminUsername(); |
| 79 | |
| 80 | Supplier<Map<String, String>> defaultAdminSshKeys(); |
| 81 | |
| 82 | Supplier<String> passwordGenerator(); |
| 83 | |
| 84 | Function<String, String> cryptFunction(); |
| 85 | } |
| 86 | |
| 87 | public static class Builder { |
| 88 | private final Function<String, String> cryptFunction; |
| 89 | |
| 90 | public Builder() { |
| 91 | this(Sha512Crypt.function()); |
| 92 | } |
| 93 | |
| 94 | public Builder(Function<String, String> cryptFunction) { |
| 95 | this.cryptFunction = cryptFunction; |
| 96 | } |
| 97 | |
| 98 | private String adminUsername; |
| 99 | private String adminPublicKey; |
| 100 | private File adminPublicKeyFile; |
| 101 | private String adminPrivateKey; |
| 102 | private File adminPrivateKeyFile; |
| 103 | private String adminPassword; |
| 104 | private String loginPassword; |
| 105 | private boolean lockSsh = true; |
| 106 | private boolean grantSudoToAdminUser = true; |
| 107 | private boolean authorizeAdminPublicKey = true; |
| 108 | private boolean installAdminPrivateKey = false; |
| 109 | private boolean resetLoginPassword = true; |
| 110 | |
| 111 | public AdminAccess.Builder adminUsername(String adminUsername) { |
| 112 | this.adminUsername = adminUsername; |
| 113 | return this; |
| 114 | } |
| 115 | |
| 116 | public AdminAccess.Builder adminPassword(String adminPassword) { |
| 117 | this.adminPassword = adminPassword; |
| 118 | return this; |
| 119 | } |
| 120 | |
| 121 | public AdminAccess.Builder loginPassword(String loginPassword) { |
| 122 | this.loginPassword = loginPassword; |
| 123 | return this; |
| 124 | } |
| 125 | |
| 126 | public AdminAccess.Builder lockSsh(boolean lockSsh) { |
| 127 | this.lockSsh = lockSsh; |
| 128 | return this; |
| 129 | } |
| 130 | |
| 131 | public AdminAccess.Builder resetLoginPassword(boolean resetLoginPassword) { |
| 132 | this.resetLoginPassword = resetLoginPassword; |
| 133 | return this; |
| 134 | } |
| 135 | |
| 136 | public AdminAccess.Builder authorizeAdminPublicKey(boolean authorizeAdminPublicKey) { |
| 137 | this.authorizeAdminPublicKey = authorizeAdminPublicKey; |
| 138 | return this; |
| 139 | } |
| 140 | |
| 141 | public AdminAccess.Builder installAdminPrivateKey(boolean installAdminPrivateKey) { |
| 142 | this.installAdminPrivateKey = installAdminPrivateKey; |
| 143 | return this; |
| 144 | } |
| 145 | |
| 146 | public AdminAccess.Builder grantSudoToAdminUser(boolean grantSudoToAdminUser) { |
| 147 | this.grantSudoToAdminUser = grantSudoToAdminUser; |
| 148 | return this; |
| 149 | } |
| 150 | |
| 151 | public AdminAccess.Builder adminPublicKey(File adminPublicKey) { |
| 152 | this.adminPublicKeyFile = adminPublicKey; |
| 153 | this.adminPublicKey = null; |
| 154 | return this; |
| 155 | } |
| 156 | |
| 157 | public AdminAccess.Builder adminPublicKey(String adminPublicKey) { |
| 158 | this.adminPublicKey = adminPublicKey; |
| 159 | this.adminPublicKeyFile = null; |
| 160 | return this; |
| 161 | } |
| 162 | |
| 163 | public AdminAccess.Builder adminPrivateKey(File adminPrivateKey) { |
| 164 | this.adminPrivateKeyFile = adminPrivateKey; |
| 165 | this.adminPrivateKey = null; |
| 166 | return this; |
| 167 | } |
| 168 | |
| 169 | public AdminAccess.Builder adminPrivateKey(String adminPrivateKey) { |
| 170 | this.adminPrivateKey = adminPrivateKey; |
| 171 | this.adminPrivateKeyFile = null; |
| 172 | return this; |
| 173 | } |
| 174 | |
| 175 | public AdminAccess build() { |
| 176 | return new AdminAccess(buildConfig()); |
| 177 | } |
| 178 | |
| 179 | protected Config buildConfig() { |
| 180 | try { |
| 181 | String adminPublicKey = this.adminPublicKey; |
| 182 | if (adminPublicKey == null && adminPublicKeyFile != null) |
| 183 | adminPublicKey = Files.toString(adminPublicKeyFile, UTF_8); |
| 184 | String adminPrivateKey = this.adminPrivateKey; |
| 185 | if (adminPrivateKey == null && adminPrivateKeyFile != null) |
| 186 | adminPrivateKey = Files.toString(adminPrivateKeyFile, UTF_8); |
| 187 | return new Config(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword, lockSsh, |
| 188 | grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword, |
| 189 | cryptFunction); |
| 190 | } catch (IOException e) { |
| 191 | Throwables.propagate(e); |
| 192 | return null; |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | protected static class Config { |
| 198 | private final String adminUsername; |
| 199 | private final String adminPublicKey; |
| 200 | private final String adminPrivateKey; |
| 201 | private final String adminPassword; |
| 202 | private final String loginPassword; |
| 203 | private final boolean lockSsh; |
| 204 | private final boolean grantSudoToAdminUser; |
| 205 | private final boolean authorizeAdminPublicKey; |
| 206 | private final boolean installAdminPrivateKey; |
| 207 | private final boolean resetLoginPassword; |
| 208 | private final Function<String, String> cryptFunction; |
| 209 | private final Credentials adminCredentials; |
| 210 | |
| 211 | protected Config(@Nullable String adminUsername, @Nullable String adminPublicKey, |
| 212 | @Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword, |
| 213 | boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey, |
| 214 | boolean installAdminPrivateKey, boolean resetLoginPassword, Function<String, String> cryptFunction) { |
| 215 | this.adminUsername = adminUsername; |
| 216 | this.adminPublicKey = adminPublicKey; |
| 217 | this.adminPrivateKey = adminPrivateKey; |
| 218 | this.adminPassword = adminPassword; |
| 219 | this.loginPassword = loginPassword; |
| 220 | this.lockSsh = lockSsh; |
| 221 | this.grantSudoToAdminUser = grantSudoToAdminUser; |
| 222 | this.authorizeAdminPublicKey = authorizeAdminPublicKey; |
| 223 | this.installAdminPrivateKey = installAdminPrivateKey; |
| 224 | this.resetLoginPassword = resetLoginPassword; |
| 225 | this.cryptFunction = cryptFunction; |
| 226 | if (adminUsername != null && authorizeAdminPublicKey && adminPrivateKey != null) |
| 227 | this.adminCredentials = new Credentials(adminUsername, adminPrivateKey); |
| 228 | else |
| 229 | this.adminCredentials = null; |
| 230 | } |
| 231 | |
| 232 | public String getAdminUsername() { |
| 233 | return adminUsername; |
| 234 | } |
| 235 | |
| 236 | public String getAdminPublicKey() { |
| 237 | return adminPublicKey; |
| 238 | } |
| 239 | |
| 240 | public String getAdminPrivateKey() { |
| 241 | return adminPrivateKey; |
| 242 | } |
| 243 | |
| 244 | public String getAdminPassword() { |
| 245 | return adminPassword; |
| 246 | } |
| 247 | |
| 248 | public String getLoginPassword() { |
| 249 | return loginPassword; |
| 250 | } |
| 251 | |
| 252 | public boolean shouldLockSsh() { |
| 253 | return lockSsh; |
| 254 | } |
| 255 | |
| 256 | public boolean shouldGrantSudoToAdminUser() { |
| 257 | return grantSudoToAdminUser; |
| 258 | } |
| 259 | |
| 260 | public boolean shouldAuthorizeAdminPublicKey() { |
| 261 | return authorizeAdminPublicKey; |
| 262 | } |
| 263 | |
| 264 | public boolean shouldInstallAdminPrivateKey() { |
| 265 | return installAdminPrivateKey; |
| 266 | } |
| 267 | |
| 268 | public boolean shouldResetLoginPassword() { |
| 269 | return resetLoginPassword; |
| 270 | } |
| 271 | |
| 272 | public Function<String, String> getCryptFunction() { |
| 273 | return cryptFunction; |
| 274 | } |
| 275 | |
| 276 | public Credentials getAdminCredentials() { |
| 277 | return adminCredentials; |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | private Config config; |
| 282 | |
| 283 | protected AdminAccess(Config in) { |
| 284 | this.config = checkNotNull(in, "in"); |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * |
| 289 | * @return new credentials or null if unchanged or unavailable |
| 290 | */ |
| 291 | @Nullable |
| 292 | public Credentials getAdminCredentials() { |
| 293 | return config.getAdminCredentials(); |
| 294 | } |
| 295 | |
| 296 | @Nullable |
| 297 | public String getAdminPassword() { |
| 298 | return config.getAdminPassword(); |
| 299 | } |
| 300 | |
| 301 | public boolean shouldGrantSudoToAdminUser() { |
| 302 | return config.shouldGrantSudoToAdminUser(); |
| 303 | } |
| 304 | |
| 305 | @Override |
| 306 | public Iterable<String> functionDependencies(OsFamily family) { |
| 307 | return ImmutableList.of(); |
| 308 | } |
| 309 | |
| 310 | public AdminAccess init(Configuration configuration) { |
| 311 | Builder builder = AdminAccess.builder(configuration.cryptFunction()); |
| 312 | builder.adminUsername(config.getAdminUsername() != null ? config.getAdminUsername() : configuration |
| 313 | .defaultAdminUsername().get()); |
| 314 | builder.adminPassword(config.getAdminPassword() != null ? config.getAdminPassword() : configuration |
| 315 | .passwordGenerator().get()); |
| 316 | Map<String, String> adminSshKeys = (config.getAdminPublicKey() != null && config.getAdminPrivateKey() != null) ? ImmutableMap |
| 317 | .of("public", config.getAdminPublicKey(), "private", config.getAdminPrivateKey()) |
| 318 | : configuration.defaultAdminSshKeys().get(); |
| 319 | builder.adminPublicKey(adminSshKeys.get("public")); |
| 320 | builder.adminPrivateKey(adminSshKeys.get("private")); |
| 321 | builder.loginPassword(config.getLoginPassword() != null ? config.getLoginPassword() : configuration |
| 322 | .passwordGenerator().get()); |
| 323 | builder.grantSudoToAdminUser(config.shouldGrantSudoToAdminUser()); |
| 324 | builder.authorizeAdminPublicKey(config.shouldAuthorizeAdminPublicKey()); |
| 325 | builder.installAdminPrivateKey(config.shouldInstallAdminPrivateKey()); |
| 326 | builder.lockSsh(config.shouldLockSsh()); |
| 327 | builder.resetLoginPassword(config.shouldResetLoginPassword()); |
| 328 | this.config = builder.buildConfig(); |
| 329 | return this; |
| 330 | } |
| 331 | |
| 332 | @Override |
| 333 | public String render(OsFamily family) { |
| 334 | checkNotNull(family, "family"); |
| 335 | if (family == OsFamily.WINDOWS) |
| 336 | throw new UnsupportedOperationException("windows not yet implemented"); |
| 337 | checkNotNull(config.getAdminUsername(), "adminUsername"); |
| 338 | checkNotNull(config.getAdminPassword(), "adminPassword"); |
| 339 | checkNotNull(config.getAdminPublicKey(), "adminPublicKey"); |
| 340 | checkNotNull(config.getAdminPrivateKey(), "adminPrivateKey"); |
| 341 | checkNotNull(config.getLoginPassword(), "loginPassword"); |
| 342 | |
| 343 | ImmutableList.Builder<Statement> statements = ImmutableList.<Statement> builder(); |
| 344 | UserAdd.Builder userBuilder = UserAdd.builder(); |
| 345 | userBuilder.login(config.getAdminUsername()); |
| 346 | if (config.shouldAuthorizeAdminPublicKey()) |
| 347 | userBuilder.authorizeRSAPublicKey(config.getAdminPublicKey()); |
| 348 | userBuilder.password(config.getAdminPassword()); |
| 349 | if (config.shouldInstallAdminPrivateKey()) |
| 350 | userBuilder.installRSAPrivateKey(config.getAdminPrivateKey()); |
| 351 | if (config.shouldGrantSudoToAdminUser()) { |
| 352 | statements.add(SudoStatements.createWheel()); |
| 353 | userBuilder.group("wheel"); |
| 354 | } |
| 355 | statements.add(userBuilder.build().cryptFunction(config.getCryptFunction())); |
| 356 | if (config.shouldLockSsh()) |
| 357 | statements.add(SshStatements.lockSshd()); |
| 358 | if (config.shouldResetLoginPassword()) { |
| 359 | statements.add(ShadowStatements.resetLoginUserPasswordTo(config.getLoginPassword()).cryptFunction( |
| 360 | config.getCryptFunction())); |
| 361 | } |
| 362 | return new StatementList(statements.build()).render(family); |
| 363 | } |
| 364 | } |