Skip to content
Merged
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
124 changes: 124 additions & 0 deletions CreateProfile.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Based on code developed by Josh Rickard (@MS_dministrator) and Thom Schumacher (@driberif)
# Location: https://gist.github.com/crshnbrn66/7e81bf20408c05ddb2b4fdf4498477d8

#function to register a native method
function Register-NativeMethod
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]$dll,

# Param2 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=1)]
[string]
$methodSignature
)

$script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; }
}

#function to add native method
function Add-NativeMethods
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param($typeName = 'NativeMethods')

$nativeMethodsCode = $script:nativeMethods | ForEach-Object { "
[DllImport(`"$($_.Dll)`")]
public static extern $($_.Signature);
" }

Add-Type @"
using System;
using System.Text;
using System.Runtime.InteropServices;
public static class $typeName {
$nativeMethodsCode
}
"@
}

#Main function to create the new user profile
function New-UserWithProfile {
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]$UserName,

# Param2 help description
[Parameter(ValueFromPipelineByPropertyName=$true,
Position=1)]
[SecureString]
$Password,

[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=2)]
[string]
$Description
)

Write-Verbose "Creating local user $Username";

try
{
if($null -eq $Password) {
New-LocalUser -Name $UserName -NoPassword -Description "$Description" -AccountNeverExpires
} else {
New-LocalUser -Name $UserName -Password $Password -Description "$Description" -AccountNeverExpires
}
}
catch
{
Write-Error $_.Exception.Message;
break;
}
$methodName = 'UserEnvCP'
$script:nativeMethods = @();

if (-not ([System.Management.Automation.PSTypeName]$MethodName).Type)
{
Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,`
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,`
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)";

Add-NativeMethods -typeName $MethodName;
}

$localUser = New-Object System.Security.Principal.NTAccount("$UserName");
$userSID = $localUser.Translate([System.Security.Principal.SecurityIdentifier]);
$sb = new-object System.Text.StringBuilder(260);
$pathLen = $sb.Capacity;

Write-Verbose "Creating user profile for $Username";

try
{
[UserEnvCP]::CreateProfile($userSID.Value, $Username, $sb, $pathLen) | Out-Null;
}
catch
{
Write-Error $_.Exception.Message;
break;
}

$profilePath = $sb.ToString()
Write-Verbose "Profile created at $profilePath"
}
83 changes: 83 additions & 0 deletions Dockerfile-windows
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# escape=`

# The MIT License
#
# Copyright (c) 2019, Alex Earl
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

ARG WINDOWS_DOCKER_TAG=1809

FROM openjdk:8-jdk-windowsservercore-$WINDOWS_DOCKER_TAG
LABEL MAINTAINER="Alex Earl <slide.o.mix@gmail.com>"

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

ARG user=jenkins

ARG JENKINS_AGENT_HOME=C:/Users/${user}

ARG OPENSSH_VERSION=v8.0.0.0p1-Beta

ENV JENKINS_AGENT_USER ${user}

ENV JENKINS_AGENT_HOME ${JENKINS_AGENT_HOME}

COPY CreateProfile.psm1 C:/

#create jenkins user
RUN Import-Module -Force C:/CreateProfile.psm1 ; `
New-UserWithProfile -UserName $env:user -Description 'Jenkins Agent User' ; `
Remove-Item -Force C:/CreateProfile.psm1 ; `
Set-LocalUser -Name $env:user -PasswordNeverExpires $true ; `
New-Item -Type Directory -Path "C:\ProgramData\Jenkins" | Out-Null

# setup SSH server
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps enabling Tls12 could be upstreamed?

Here is a PowerShell script version of it, could be converted to Docker run :)

#requires -runasadministrator

# set strong cryptography on 64 bit .Net Framework
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v2.0.50727' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v2.0.50727' -Name 'SystemDefaultTlsVersions' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value '1' -Type DWord

# set strong cryptography on 32 bit .Net Framework
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727' -Name 'SystemDefaultTlsVersions' -Value '1' -Type DWord
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value '1' -Type DWord

Invoke-WebRequest -Uri "https://github.com/PowerShell/Win32-OpenSSH/releases/download/${env:OPENSSH_VERSION}/OpenSSH-Win64.zip -OutFile C:/openssh.zip -UseBasicParsing ; `
Expand-Archive c:/openssh.zip 'C:/Program Files' ; `
Remove-Item C:/openssh.zip ; `
$env:PATH = '{0};{1}' -f $env:PATH,'C:\Program Files\OpenSSH-Win64' ; `
& 'C:/Program Files/OpenSSH-Win64/Install-SSHd.ps1' ; `
New-Item -Type Directory -Path 'C:\ProgramData\ssh' | Out-Null ; `
Copy-Item 'C:\Program Files\OpenSSH-Win64\sshd_config_default' 'C:\ProgramData\ssh\sshd_config' ; `
$content = Get-Content -Path "C:\ProgramData\ssh\sshd_config" ; `
$content | ForEach-Object { $_ -replace '#PermitRootLogin.*','PermitRootLogin no' `
-replace '#PasswordAuthentication.*','PasswordAuthentication no' `
-replace '#PermitEmptyPasswords.*','PermitEmptyPasswords no' `
-replace '#PubkeyAuthentication.*','PubkeyAuthentication yes' `
-replace '#SyslogFacility.*','SyslogFacility LOCAL0' `
-replace '#LogLevel.*','LogLevel DEBUG3' `
-replace 'Match Group administrators','' `
-replace '(\s*)AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys','' `
} | `
Set-Content -Path "C:\ProgramData\ssh\sshd_config" ; `
Add-Content -Path "C:\ProgramData\ssh\sshd_config" -Value 'ChallengeResponseAuthentication no' ; `
Add-Content -Path "C:\ProgramData\ssh\sshd_config" -Value 'HostKeyAgent \\.\pipe\openssh-ssh-agent' ; `
New-Item -Path HKLM:\SOFTWARE -Name OpenSSH -Force | Out-Null ; `
New-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType string -Force | Out-Null

VOLUME "${JENKINS_AGENT_HOME}" "C:\Users\${user}\AppData\Local\Temp"
WORKDIR "${JENKINS_AGENT_HOME}"

COPY setup-sshd.ps1 C:/ProgramData/Jenkins/setup-sshd.ps1

EXPOSE 22

ENTRYPOINT ["powershell.exe", "-NoExit", "-Command", "& C:/ProgramData/Jenkins/setup-sshd.ps1"]
46 changes: 46 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* NOTE: this Pipeline mainly aims at catching mistakes (wrongly formed Dockerfile, etc.)
* This Pipeline is *not* used for actual image publishing.
* This is currently handled through Automated Builds using standard Docker Hub feature
*/
pipeline {
agent { label 'linux' }

options {
timeout(time: 2, unit: 'MINUTES')
buildDiscarder(logRotator(daysToKeepStr: '10'))
timestamps()
}

triggers {
pollSCM('H/24 * * * *') // once a day in case some hooks are missed
}

stages {
stage('Build Docker Image') {
parallel {
stage('Windows') {
agent {
label "windock"
}
steps {
deleteDir()
checkout scm
powershell "& ./make.ps1"
}
}
stage('Linux') {
agent {
label "docker&&linux"
}
steps {
deleteDir()
checkout scm
sh "make build"
}
}
}
}
}
}

// vim: ft=groovy
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME:=jenkins4eval/ssh-slave:test

build:
docker build -t ${IMAGE_NAME} .
5 changes: 5 additions & 0 deletions make.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if([System.String]::IsNullOrWhiteSpace($env:IMAGE_NAME)) {
$env:IMAGE_NAME='jenkins4eval/ssh-agent:test-windows'
}

& docker build -f Dockerfile-windows -t $env:IMAGE_NAME .
73 changes: 73 additions & 0 deletions setup-sshd.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# The MIT License
#
# Copyright (c) 2019, Alex Earl
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# Usage:
# docker run jenkins/ssh-agent:windows <public key>
# or
# docker run -e "JENKINS_SLAVE_SSH_PUBKEY=<public key>" jenkins/ssh-agent:windows

function Write-Key($Key) {
# this writes the key and sets the permissions correctly for pubkey auth
$sshDir = '{0}\.ssh' -f $env:JENKINS_AGENT_HOME
$authorizedKeys = Join-Path $sshDir 'authorized_keys'
New-Item -Type Directory -Path $sshDir | Out-Null
icacls.exe $sshDir /setowner ${env:JENKINS_AGENT_USER} | Out-Null
icacls.exe $sshDir /grant "${env:JENKINS_AGENT_USER}:(CI)(OI)(F)" /grant "administrators:(CI)(OI)(F)" | Out-Null
icacls.exe $sshDir /inheritance:r | Out-Null

Set-Content -Path $authorizedKeys -Value "$Key" -Encoding UTF8
icacls.exe $authorizedKeys /setowner ${env:JENKINS_AGENT_USER} | Out-Null
}

# Even though we created a profile, the NTUSER.DAT file is missing
# this needs to be in the directory or Windows will not load
# the profile
if(!(Test-Path (Join-Path $env:JENKINS_AGENT_HOME 'NTUSER.DAT'))) {
Copy-Item -Path 'C:\Users\Default\NTUSER.DAT' -Destination (Join-Path $env:JENKINS_AGENT_HOME 'NTUSER.DAT')
}

# Give the user Full Access to the home directory
icacls.exe $env:JENKINS_AGENT_HOME /grant "${env:JENKINS_AGENT_USER}:(CI)(OI)(F)" | Out-Null

if($env:JENKINS_SLAVE_SSH_PUBKEY -match "^ssh-.*") {
Write-Key $env:JENKINS_SLAVE_SSH_PUBKEY
}

if($args.Length -gt 0) {
if($args[0] -match "^ssh-.*") {
Write-Key "$($args[0]) $($args[1]) $($args[2])"
$null, $null, $null, $args = $args
} else {
& "$args"
}
}



# ensure variables passed to docker container are also exposed to ssh sessions
Get-ChildItem env: | ForEach-Object { setx /m $_.Name $_.Value | Out-Null }

Start-Service sshd
while($true) {
# if we don't do this endless loop, the container exits
Start-Sleep -Seconds 60
}