Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User and credentials creation, simple security management #160

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,103 @@ the following `require` statement:
2. Config Hash - jennkins::config
3. Configure Firewall - jenkins (init.pp)
4. Outbound Jenkins Proxy Config - jenkins (init.pp)
5. Jenkins Users
6. Credentials
7. Simple security model configuration

### API-based Resources and Settings (Users, Credentials, security)

This module includes a groovy-based helper script that uses the
[Jenkins CLI](https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI) to
interact with the Jenkins API. Users, Credentials, and security model
configuration are all driven through this script.

When an API-based resource is defined, the Jenkins CLI is installed and run
against the local system (127.0.0.1). Jenkins is assumed to be listening on
port 8080, but the module is smart enough to notice if you've configured an
alternate port using jenkins::config_hash['HTTP_PORT'].

Users and credentials are Puppet-managed, meaning that changes made to them
from outside Puppet will be reset at the next puppet run. In this way, you can
ensure that certain accounts are present and have the appropriate login
credentials.

### CLI Helper

The CLI helper assumes unauthenticated access unless configured otherwise.
You can configure jenkins::cli_helper to use an SSH key on the managed system:

class {'jenkins::cli_helper':
ssh_keyfile => '/path/to/id_rsa',
}

There's an open bug in Jenkins (JENKINS-22346) that causes authentication to
fail when a key is used but authentication is disabled. Until the bug is fixed,
you may need to bootstrap jenkins out-of-band to ensure that resources and
security policy are configured in the correct order. For example:

# In puppet:
anchor {'jenkins-bootstrap-start': } ->
Class['jenkins::cli_helper'] ->
Exec[$bootstrap_script] ->
anchor {'jenkins-bootstrap-complete': }

# Code for $bootstrap_script
#!/bin/bash -e
# Generate an SSH key for the admin user
ADMIN_USER='<%= admin_user_name %>'
ADMIN_EMAIL='<%= admin_user_email %>'
ADMIN_PASSWORD='<%= admin_user_password %>'
ADMIN_FULLNAME='<%= admin_user_full_name %>'
ADMIN_SSH_KEY='<%= admin_ssh_keyfile %>'
JENKINS_CLI='<%= jenkins_libdir %>/jenkins-cli.jar'
PUPPET_HELPER='<%= jenkins_libdir %>/puppet_helper.groovy'
HELPER="java -jar $JENKINS_CLI -s http://127.0.0.1:8080 groovy $PUPPET_HELPER"
DONEFILE='<%= jenkins_libdir %>/jenkins-bootstrap.done'

ADMIN_PUBKEY="$(cat ${ADMIN_SSH_KEY}.pub)"

# Create the admin user, passing no credentials
$HELPER create_or_update_user "$ADMIN_USER" "$ADMIN_EMAIL" "$ADMIN_PASSWORD" "$ADMIN_FULLNAME" "$ADMIN_PUBKEY"
# Enable security. After this, credentials will be required.
$HELPER set_security full_control

touch $DONEFILE

#### Users

Email and password are required.

Create a `johndoe` user account whose full name is "Managed by Puppet":

jenkins::user {'johndoe':
email => 'jdoe@example.com',
password => 'changeme',
}

### Credentials

Password is required. For ssh credentials, `password` is the key passphrase (or
'' if there is none). `private_key_or_path` is the text of key itself or an
absolute path to a key file on the managed system.

Create ssh credentials named 'github-deploy-key', providing an unencrypted
private key:

jenkins::credentials {'github-deploy-key':
password => '',
private_key_or_path => hiera('::github_deploy_key'),
}

### Configuring Security

The Jenkins security model can be set to one of two modes:

* `full_control` - Users have full control after login. Authentication uses
Jenkins' built-in user database.
* `unsecured` - Authentication is not required.

Jenkins security is not managed by puppet unless jenkins::security is defined.

## Using from Github / source

Expand Down
271 changes: 271 additions & 0 deletions files/puppet_helper.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
// Copyright 2010 VMware, Inc.
// Copyright 2011 Fletcher Nichol
// Copyright 2013-2014 Chef Software, Inc.
// Copyright 2014 RetailMeNot, Inc.
//
// 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.

import com.cloudbees.jenkins.plugins.sshcredentials.impl.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.*;
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.*;
import com.cloudbees.plugins.credentials.common.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.plugins.credentials.domains.*;
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.plugins.credentials.impl.*;
import hudson.plugins.sshslaves.*;
import jenkins.model.*;

class InvalidAuthenticationStrategy extends Exception{}

///////////////////////////////////////////////////////////////////////////////
// Actions
///////////////////////////////////////////////////////////////////////////////

class Actions {
Actions(out) { this.out = out }
def out

private credentials_for_username(String username) {
def username_matcher = CredentialsMatchers.withUsername(username)
def available_credentials =
CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class,
Jenkins.getInstance(),
hudson.security.ACL.SYSTEM,
new SchemeRequirement("ssh")
)

return CredentialsMatchers.firstOrNull(
available_credentials,
username_matcher
)
}

/////////////////////////
// create or update user
/////////////////////////
void create_or_update_user(String user_name, String email, String password="", String full_name="", String public_keys="") {
def user = hudson.model.User.get(user_name)
user.setFullName(full_name)

def email_param = new hudson.tasks.Mailer.UserProperty(email)
user.addProperty(email_param)

def pw_param = hudson.security.HudsonPrivateSecurityRealm.Details.fromPlainPassword(password)
user.addProperty(pw_param)

if ( public_keys != "" ) {
def keys_param = new org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl(public_keys)
user.addProperty(keys_param)
}

user.save()
}

/////////////////////////
// delete user
/////////////////////////
void delete_user(String user_name) {
def user = hudson.model.User.get(user_name, false)
if (user != null) {
user.delete()
}
}

/////////////////////////
// current user
/////////////////////////
void user_info(String user_name) {
def user = hudson.model.User.get(user_name, false)

if(user == null) {
return null
}

def user_id = user.getId()
def name = user.getFullName()

def email_address = null
def emailProperty = user.getProperty(hudson.tasks.Mailer.UserProperty)
if(emailProperty != null) {
email_address = emailProperty.getAddress()
}

def keys = null
def keysProperty = user.getProperty(org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl)
if(keysProperty != null) {
keys = keysProperty.authorizedKeys.split('\\s+')
}

def token = null
def tokenProperty = user.getProperty(jenkins.security.ApiTokenProperty.class)
if (tokenProperty != null) {
token = tokenProperty.getApiToken()
}

def builder = new groovy.json.JsonBuilder()
builder {
id user_id
full_name name
email email_address
api_token token
public_keys keys
}

out.println(builder)
}

/////////////////////////
// create credentials
/////////////////////////
void create_or_update_credentials(String username, String password, String description="", String private_key="") {
def global_domain = Domain.global()
def credentials_store =
Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()

def credentials
if (private_key == "" ) {
credentials = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL,
null,
description,
username,
password
)
} else {
def key_source
if (private_key.startsWith('-----BEGIN')) {
key_source = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(private_key)
} else {
key_source = new BasicSSHUserPrivateKey.FileOnMasterPrivateKeySource(private_key)
}
credentials = new BasicSSHUserPrivateKey(
CredentialsScope.GLOBAL,
null,
username,
key_source,
password,
description
)
}

// Create or update the credentials in the Jenkins instance
def existing_credentials = credentials_for_username(username)

if(existing_credentials != null) {
credentials_store.updateCredentials(
global_domain,
existing_credentials,
credentials
)
} else {
credentials_store.addCredentials(global_domain, credentials)
}
}

//////////////////////////
// delete credentials
//////////////////////////
void delete_credentials(String username) {
def existing_credentials = credentials_for_username(username)

if(existing_credentials != null) {
def global_domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def credentials_store =
Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
credentials_store.removeCredentials(
global_domain,
existing_credentials
)
}
}

////////////////////////
// current credentials
////////////////////////
void credential_info(String username) {
def credentials = credentials_for_username(username)

if(credentials == null) {
return null
}

def current_credentials = [
id:credentials.id,
description:credentials.description,
username:credentials.username
]

if ( credentials.hasProperty('password') ) {
current_credentials['password'] = credentials.password.plainText
} else {
current_credentials['private_key'] = credentials.privateKey
current_credentials['passphrase'] = credentials.passphrase.plainText
}

def builder = new groovy.json.JsonBuilder(current_credentials)
out.println(builder)
}

////////////////////////
// set_security
////////////////////////
/*
* Set up security for the Jenkins instance. This currently supports
* only a small number of configurations. If authentication is enabled, it
* uses the internal user database.
*/
void set_security(String security_model) {
def instance = Jenkins.getInstance()

if (security_model == 'disabled') {
instance.disableSecurity()
return null
}

def strategy
def realm
switch (security_model) {
case 'full_control':
strategy = new hudson.security.FullControlOnceLoggedInAuthorizationStrategy()
realm = new hudson.security.HudsonPrivateSecurityRealm(false, false, null)
break
case 'unsecured':
strategy = new hudson.security.AuthorizationStrategy.Unsecured()
realm = new hudson.security.HudsonPrivateSecurityRealm(false, false, null)
break
default:
throw new InvalidAuthenticationStrategy()
}
instance.setAuthorizationStrategy(strategy)
instance.setSecurityRealm(realm)
}
} // class Actions

///////////////////////////////////////////////////////////////////////////////
// CLI Argument Processing
///////////////////////////////////////////////////////////////////////////////

actions = new Actions(out)
action = args[0]
if (args.length < 2) {
actions."$action"()
} else {
actions."$action"(*args[1..-1])
}
4 changes: 2 additions & 2 deletions manifests/cli.pp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
fail("Use of private class ${name} by ${caller_module_name}")
}

$jar = '/usr/lib/jenkins/jenkins-cli.jar'
$extract_jar = 'unzip /usr/lib/jenkins/jenkins.war WEB-INF/jenkins-cli.jar'
$jar = "${jenkins::libdir}/jenkins-cli.jar"
$extract_jar = "unzip ${jenkins::libdir}/jenkins.war WEB-INF/jenkins-cli.jar"
$move_jar = "mv WEB-INF/jenkins-cli.jar ${jar}"
$remove_dir = 'rm -rf WEB-INF'

Expand Down
Loading