I have this groovy script to initialize my Jenkins server:
def hudsonRealm = new HudsonPrivateSecurityRealm(false) ;
hudsonRealm.createAccount('admin','admin')
instance.setSecurityRealm(hudsonRealm)
def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
instance.setAuthorizationStrategy(strategy)
This will create a "admin/admin" user at initial startup of my Jenkins instance.
How do I complete this to add my SSH public key to the user "admin"?
The objective is to be able to use the "ssh admin#jenkins" to run commands after the config, like explained here: https://www.jenkins.io/doc/book/managing/cli/#ssh
PS: I know I can do it manually through the web interface, I am looking for a mean to do that by script.
I found this set of scripts here: https://gist.github.com/nigimaster/6a014c62b444fe7a7744304d66881451
I post here the full commands with comments:
//
// Create initial admin with username/password = admin/password
//
// thanks to https://gist.github.com/hayderimran7/50cb1244cc1e856873a4
//
def hudsonRealm = new HudsonPrivateSecurityRealm(false) ;
hudsonRealm.createAccount('admin','password')
instance.setSecurityRealm(hudsonRealm)
def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
instance.setAuthorizationStrategy(strategy)
//
// Add ssh key to admin
//
// thanks to #nigimaster https://gist.github.com/nigimaster/6a014c62b444fe7a7744304d66881451
//
def user = hudson.model.User.get('admin')
user.addProperty(new org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl(
/** your public key content: ssh-rsa AA... */
))
user.save()
//
// Allow connection over ssh
//
// thanks to https://github.com/jenkins-infra/jenkins-infra/blob/staging/dist/profile/files/buildmaster/enable-ssh-port.groovy
//
def sshConfig = instance.getDescriptor('org.jenkinsci.main.modules.sshd.SSHD')
sshConfig.port = 22
sshConfig.save()
This file is saved at /usr/share/jenkins/ref/init.groovy.d/customize.groovy (at least in the docker file).
Security advice: you should parametrize your groovy file to not have the passwords stored in it. Public key should not be a problem.
Using "Configuration as code", this is possible with this config:
jenkins:
authorizationStrategy:
loggedInUsersCanDoAnything:
allowAnonymousRead: false
securityRealm:
local:
allowsSignup: false
users:
- id: admin
name: ${ADMIN_USER}
password: ${ADMIN_PASSWORD}
properties:
- sshPublicKey:
authorizedKeys: ${readFile:/conf/generated/authorized_keys}
security:
sSHD:
port: 22
See https://github.com/jenkinsci/configuration-as-code-plugin
Related
We have a tag policy in place such that each resource group has a Tech-Owner-Email and Tech-Owner-Name tag.
We want to be notified when people leaving the org have resource groups under their name.
What's a good way to find groups belonging to people who have recently left?
We will want this process to run periodically and to notify us with any results (email, teams, whatever works)
The graph API has an endpoint that lists recently deleted users (docs)
https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user
This can be polled via the Azure CLI to get the list of people, then az cli can also be used to get the list of tags from resource groups.
The script
#!/usr/bin/pwsh
Write-Host "Fetching deleted users"
$deletedUsers = az rest `
--method "GET" `
--url "https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user" `
--headers "Content-Type=application/json" | ConvertFrom-Json
if ($deletedUsers.value.count -eq 0)
{
Write-Warning "No deleted users found"
return
}
Write-Host "Gathering subscriptions"
$subs = az account list | ConvertFrom-Json
$found=0
foreach ($sub in $subs)
{
Write-Host "Gathering resource group tags for subscription $($sub.name)"
$groups = az group list --subscription $sub.id | ConvertFrom-Json
foreach ($group in $groups)
{
if (
$group.tags."Tech-Owner-Email" -in $deletedUsers.value.mail -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.businessPhones -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.displayName -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.givenName -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.id -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.jobTitle -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.mail -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.mobilePhone -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.officeLocation -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.preferredLanguage -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.surname -or
$group.tags."Tech-Owner-Name" -in $deletedUsers.value.userPrincipalName
)
{
Write-Host "Found group `"$($group.name)`" belonging to `"$($group.tags."Tech-Owner-Name")`" <$($group.tags."Tech-Owner-Email")>"
$found++
}
}
}
if ($found -gt 0)
{
throw "Found $found groups belonging to people who are no longer with us"
}
Note that $deletedUsers.value is a list of objects, but $deletedUsers.value.mail expands to a list of strings :D
The pipeline that runs every day
From there, you can use Azure DevOps pipelines to run this script periodically
trigger:
branches:
include:
- main
schedules:
- cron: "0 12 * * *"
displayName: Daily check
branches:
include:
- main
variables:
- group: deleted-owners-checker
steps:
- task: PowerShell#2
displayName: az login
inputs:
pwsh: true
targetType: inline
script: az login --service-principal -u $Env:applicationId -p $Env:password --tenant $Env:tenantId
env:
applicationId: $(applicationId)
objectId: $(objectId)
password: $(password)
tenantId: $(tenantId)
- task: PowerShell#2
displayName: run script
inputs:
targetType: filePath
filePath: scripts/listdeleted.ps1 # our repo has a folder called scripts containing the relevant script, update this based on your own
- task: PowerShell#2
displayName: az logout
inputs:
pwsh: true
targetType: inline
script: az logout
condition: always() # log out even if other tasks fail
This necessitates having a service principal that can be logged in during pipeline execution and has permissions to read your resource groups and has User.Read (Application) graph permissions. The credentials for the service principal can be passed to the pipeline using the Azure DevOps Library connected to an Azure KeyVault.
You can alternatively use a devops ARM service connection in a CLI task instead of manually logging in to the CLI, but I've had issues with this in the past so I prefer to manually log in.
The notification
Note that the scripts exits with an error if any groups belonging to gone-people are found, this lets you use pipeline-failed emails as your notification system. A better method would be Teams webhooks in the PowerShell script, but that's farther than I've gotten with this.
The infrastructure-as-code
I prefer to automate the creation of the service principal and key vault using Terraform.
This should take care of creating the key vault, creating the service principal, giving the service principal Reader perms on the resource groups (inherited), and giving the service principal the graph permission needed to query deleted users (might need admin consent).
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.23.0"
}
azuread = {
source = "hashicorp/azuread"
version = ">= 2.21.0"
}
}
}
data "azurerm_resource_group" "main" {
name = "my-rg-where-i-want-my-key-vault-to-go" # change me!
}
data "azurerm_client_config" "main" {}
resource "azuread_application" "main" {
display_name = "deleted-owners-checker"
owners = [
data.azurerm_client_config.main.object_id
]
required_resource_access {
resource_app_id = "00000003-0000-0000-c000-000000000000" # microsoft graph
resource_access {
# User.Read.All
id = "df021288-bdef-4463-88db-98f22de89214"
type = "Role"
}
}
}
resource "azuread_service_principal" "main" {
application_id = azuread_application.main.application_id
owners = azuread_application.main.owners
}
resource "azuread_application_password" "main" {
application_object_id = azuread_application.main.object_id
}
resource "azurerm_role_assignment" "main" {
principal_id = azuread_service_principal.main.object_id
scope = "/providers/Microsoft.Management/managementGroups/00000000-000000000-000000000-0000000" # management group that contains the resource groups you care about, change me!
role_definition_name = "Reader"
}
resource "azurerm_key_vault" "main" {
resource_group_name = data.azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.main.tenant_id
location = "canadacentral"
sku_name = "standard"
name = "deleted-owners-checker"
lifecycle {
ignore_changes = [
tags
]
}
}
resource "azurerm_key_vault_access_policy" "me" {
secret_permissions = ["Get", "List", "Set", "Delete", "Purge"]
certificate_permissions = ["Get", "List", "Create", "Delete", "Import", "Purge"]
key_permissions = ["Get", "List", "Create", "Delete", "Import", "Purge"]
key_vault_id = azurerm_key_vault.main.id
tenant_id = azurerm_key_vault.main.tenant_id
object_id = data.azurerm_client_config.main.object_id
}
resource "azurerm_key_vault_access_policy" "devops" {
secret_permissions = ["Get", "List"]
key_vault_id = azurerm_key_vault.main.id
tenant_id = azurerm_key_vault.main.tenant_id
object_id = "0000-0000-0000-0000"
# You would think this would be the object ID of the principal used by the service connection used when connecting the key vault to the Azure DevOps Library
# In reality, you're better off creating the key vault access policy from the Azure DevOps web interface, since the object ID was different for us when we tried
# The azure pipeline should let you know if the object ID is wrong. We got a pipeline error like this when we first tried. The error should give you the correct object ID to use
# objectId: "The user, group or application 'appid=***;oid=5555-55555-5555-5555;iss=https://sts.windows.net/zzzz-zzzz-zzzz-zzzz/' does not have secrets get permission on key vault 'deleted-owners-checker;location=canadacentral'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287. The specified Azure service connection needs to have Get, List secret management permissions on the selected key vault. To set these permissions, download the ProvisionKeyVaultPermissions.ps1 script from build/release logs and execute it, or set them from the Azure portal."
# applicationId: "The user, group or application 'appid=***;oid=5555-55555-5555-5555;iss=https://sts.windows.net/zzzz-zzzz-zzzz-zzzz/' does not have secrets get permission on key vault 'deleted-owners-checker;location=canadacentral'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287. The specified Azure service connection needs to have Get, List secret management permissions on the selected key vault. To set these permissions, download the ProvisionKeyVaultPermissions.ps1 script from build/release logs and execute it, or set them from the Azure portal."
# tenantId: "The user, group or application 'appid=***;oid=5555-55555-5555-5555;iss=https://sts.windows.net/zzzz-zzzz-zzzz-zzzz/' does not have secrets get permission on key vault 'deleted-owners-checker;location=canadacentral'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287. The specified Azure service connection needs to have Get, List secret management permissions on the selected key vault. To set these permissions, download the ProvisionKeyVaultPermissions.ps1 script from build/release logs and execute it, or set them from the Azure portal."
# password: "The user, group or application 'appid=***;oid=5555-55555-5555-5555;iss=https://sts.windows.net/zzzz-zzzz-zzzz-zzzz/' does not have secrets get permission on key vault 'deleted-owners-checker;location=canadacentral'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287. The specified Azure service connection needs to have Get, List secret management permissions on the selected key vault. To set these permissions, download the ProvisionKeyVaultPermissions.ps1 script from build/release logs and execute it, or set them from the Azure portal."
}
locals {
kv_secrets = {
objectId = azuread_application.main.object_id
tenantId = azuread_service_principal.main.application_tenant_id
applicationId = azuread_service_principal.main.application_id
password = azuread_application_password.main.value
}
}
resource "azurerm_key_vault_secret" "main" {
for_each = local.kv_secrets
name = each.key
key_vault_id = azurerm_key_vault.main.id
value = each.value
depends_on = [
azurerm_key_vault_access_policy.me
]
}
I am using a multibranch pipeline and would like to have branch specific configs. I currently have a default config which I would like to clone to a config with ID "${BRANCH_NAME}_config". I am using default jenkins file plugin so there is no jenkins code in the repository and using Config-file-provider-plugin for the config file handling and have made some progress.
I currently have the ability to create a global config with the following code:
configFileProvider([configFile(fileId: 'DEFAULT_JSON_CONFIG', variable: 'default_config_content')]) {
def instance = Jenkins.getInstance()
def provider = instance.getExtensionList('org.jenkinsci.plugins.configfiles.json.JsonConfig$JsonConfigProvider')[0]
def config = new org.jenkinsci.plugins.configfiles.json.JsonConfig("${BRANCH_NAME}_config", "Config for ${BRANCH_NAME}", "Branch config ${BRANCH_NAME}", "$default_config_content")
provider.save(config)
}
But need to create it in the job's (multipipeline) config folder
I was able to implement this with help from JENKINS-56305
def folder = Jenkins.instance.getItemByFullName('jobName');
def action = folder.getAction(org.jenkinsci.plugins.configfiles.folder.FolderConfigFileAction.class);
def store = action.getStore();
def config = new org.jenkinsci.plugins.configfiles.json.JsonConfig("XXXX_config", "Config for XXXX", "Branch config XXXX", "{A:B}");
// save the new config
store.save(config);
// get the new config
def jsonConfig = store.getById("XXXX_config");
// remove the config
store.remove("XXXX_config");
i try to 100% automating the deployment of Jenkins with Keycloak plugin with Docker-compose. The objectiv is that we do not want to do anything but run a single command.
To automate Jenkins, I tried to use the Jenkins API but the Groovy script seems to be the best and easiest solution. The problem is that I am not a developper ...
I try something like this, but it's failed at Keycloak conf :
Failed to run script file:/var/jenkins_home/init.groovy.d/init.groovy groovy.lang.GroovyRuntimeException: Could not find matching constructor for: org.jenkinsci.plugins.KeycloakSecurityRealm(java.lang.Boolean)
import jenkins.model.*
import hudson.security.*
import org.jenkinsci.plugins.*
def instance = Jenkins.getInstance()
def env = System.getenv()
def hudsonRealm = new HudsonPrivateSecurityRealm(false)
String password = env.JENKINS_PASSWORD
hudsonRealm.createAccount("admin", password)
instance.setSecurityRealm(hudsonRealm)
instance.save()
def keycloak_realm = new KeycloakSecurityRealm(true)
instance.setSecurityRealm(keycloak_realm)
instance.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy())
instance.save()
In the end, i want to
create an admin user
configure the Keycloak plugin
set the users autorisations.
Thanks you in advance for your help :)
A possibly outdated issue, but I would like to share that I also had problems using Groovy scripts in the init.groovy.d to maintain the configurations in Jenkins, including Keycloak configurations. And the best way to solve it was through a declarative model using the Jenkins Configuration as Code (JCasC) plugin.
Examples:
Keycloak
jenkins:
securityRealm: keycloak
unclassified:
keycloakSecurityRealm:
keycloakJson: |-
{
"realm": "my-realm",
"auth-server-url": "https://my-keycloak-url/auth",
"ssl-required": "all",
"resource": "jenkins",
"public-client": true,
"confidential-port": 0
}
source: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos/keycloak
Credentials
credentials:
system:
domainCredentials:
- domain:
name: "test.com"
description: "test.com domain"
specifications:
- hostnameSpecification:
includes: "*.test.com"
credentials:
- usernamePassword:
scope: SYSTEM
id: sudo_password
username: root
password: ${SUDO_PASSWORD}
source: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos/credentials
Following solution works for me.
#!/usr/bin/env groovy
import jenkins.model.Jenkins
import hudson.security.*
import org.jenkinsci.plugins.KeycloakSecurityRealm
Jenkins jenkins = Jenkins.get()
def desc = jenkins.getDescriptor("org.jenkinsci.plugins.KeycloakSecurityRealm")
// JSON based on the keycloak configuration
desc.setKeycloakJson( "{\n" +
" \"realm\": \"myRealm\",\n" +
" \"auth-server-url\": \"https://keycloak/auth/\",\n" +
" \"ssl-required\": \"external\",\n" +
" \"resource\": \"jenkins\",\n" +
" \"public-client\": true,\n" +
" \"confidential-port\": 0\n" +
"}")
desc.save()
jenkins.setSecurityRealm(new KeycloakSecurityRealm())
def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
strategy.setAllowAnonymousRead(false)
jenkins.setAuthorizationStrategy(strategy)
jenkins.save()
im trying to undeploy/deploy an application to a Tomcat via jenkins using "system Groovy script".
For the undeploy/deploy i need credentials.
These i masked in the jenkins job via "mask passwords" (user and password)
Im not able to use these variables in my "system Groovy script" properly.
when i use the credentials in the script directly it works fine.
This is my "system Groovy script":
def ant = new AntBuilder()
//def user = build.getEnvVars()['user']
//def password = build.getEnvVars()['password']
ant.taskdef( name: 'deploy', classname: 'org.apache.catalina.ant.DeployTask' )
ant.taskdef( name: 'undeploy', classname: 'org.apache.catalina.ant.UndeployTask' )
ant.undeploy(
url:deployURL,
username:"${user}",
password:"${password}",
path:deployPath,
failonerror:true)
ant.deploy(
url:deployURL,
username:"${user}",
password:"${password}",
path:deployPath
war:warfile)
Jenkins Credentials Store Access via Groovy
I used the solution from the link above in the following way:
def credentials_store = jenkins.model.Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider')
credentials_store[0].credentials.each { ThisJenkins ->
if (ThisJenkins instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
if (ThisJenkins.username == "MyUser") {
user = ThisJenkins.username
password = ThisJenkins.password
}
}
}
later on i used the "user" and "password" for my deployment:
ant.undeploy(
url:'MyServer',
username: user,
password: password,
path:'/MyTool',
failonerror:false)
I am putting together a developer machine using ansible. In that machine i am installing jenkins.
I have created the jobs for jenkins with ansible:
- shell: "java -jar {{ jenkins.cli_jar }} -s {{ jenkins.server }} create-job \
{{ item.name }} < {{ jenkins.jobs_dir }}/{{ item.xml_file }}"
with_items: "jenkins.jobs"
And installed the plugins, via cli etc.
But now i am missing the ssh credentials for the jobs; i just want a ssh credential with user "jenkins" and that uses "From the Jenkins master ~/.ssh".
This type of credentials are the ones i am talking about:
Maybe is a groovy script but i haven't find a lot of information about it. Thanks for the help.
You can use the jenkins client from command line on the machine where the jenkins runs like:
java -jar jenkins-cli.jar -s http://localhost:8080/ groovy create-credential.groovy
with create-credential.groovy:
import jenkins.model.*
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
def addPassword = { username, new_password ->
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
Jenkins.instance
)
def c = creds.findResult { it.username == username ? it : null }
if ( c ) {
println "found credential ${c.id} for username ${c.username}"
} else {
def credentials_store = Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
def scope = CredentialsScope.GLOBAL
def description = ""
def result = credentials_store.addCredentials(
com.cloudbees.plugins.credentials.domains.Domain.global(),
new UsernamePasswordCredentialsImpl(scope, null, description, username, new_password)
)
if (result) {
println "credential added for ${username}"
} else {
println "failed to add credential for ${username}"
}
}
}
addPassword('pinky', 'narf')
This will add the global credential for user 'pinky' with password 'narf'
As of version 2.1.1 of the plugin (June 2016) this is possible through the CLI or the REST API:
https://github.com/jenkinsci/credentials-plugin/blob/master/docs/user.adoc#creating-a-credentials
from that page:
$ cat > credential.xml <<EOF
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
<scope>GLOBAL</scope>
<id>deploy-key</id>
<username>wecoyote</username>
<password>secret123</password>
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
EOF
$ curl -X POST -H content-type:application/xml -d #credential.xml \
https://jenkins.example.com/job/example-folder/credentials/store/folder/\
domain/testing/createCredentials
Jenkins credentials plugin doesn't allow credentials creation using API (https://issues.jenkins-ci.org/browse/JENKINS-28407).
A viable solution would be recording a credential creation using your prefered browser and JMeter proxy or Selenium IDE. and replaying it using JMeter CLI or saving the Selenium recorded test as a groovy script.
You may also take a look at https://github.com/jenkinsci/credentials-plugin/pull/33
Here is an example ansible task that uses "jenkins_script" instead of directly specifying the CLI or calling the REST API, and it adds the benefit of updating existing credentials.
- name: establish credentials
jenkins_script:
script: |
import jenkins.model.*
import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials
import com.cloudbees.plugins.credentials.domains.Domain
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
def domain = Domain.global()
def instance = Jenkins.instance
def credstore = instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
def existingCreds = CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class, instance).findResult {
it.username == '${username}' ? it : null
}
def newCreds = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, null,
'${description}', '${username}', '${password}')
if (existingCreds) {
credstore.updateCredentials(domain, existingCreds, newCreds)
} else {
credstore.addCredentials(domain, newCreds)
}
args:
description: "entrada credential"
username: "{{ item.username }}"
password: "{{ item.password }}"
user: "{{ entrada_user_name }}"
password: "{{ entrada_user_password }}"
with_items: "{{ entrada_cicd_credentials }}"