smart_proxy_realm_ad_plugin 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +675 -0
- data/README.md +111 -0
- data/bundler.d/realm_ad_plugin.rb +2 -0
- data/config/realm_ad.yml.example +24 -0
- data/lib/smart_proxy_realm_ad/configuration_loader.rb +23 -0
- data/lib/smart_proxy_realm_ad/plugin.rb +12 -0
- data/lib/smart_proxy_realm_ad/provider.rb +131 -0
- data/lib/smart_proxy_realm_ad/version.rb +5 -0
- data/lib/smart_proxy_realm_ad_plugin.rb +4 -0
- data/test/ad_provider_test.rb +125 -0
- data/test/api_tests.sh +14 -0
- data/test/setup_ad.md +36 -0
- data/test/test_helper.rb +7 -0
- data/test/test_radcli.rb +57 -0
- metadata +133 -0
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Status
|
2
|
+
|
3
|
+
Hi!!! Please try this out and test it. I need feedback.
|
4
|
+
|
5
|
+
# Description
|
6
|
+
This plugin adds a new realm provider for managing hosts in Active Directory.
|
7
|
+
|
8
|
+
Useful if you directly integrate with Active Directory and dont use FreeIPA.
|
9
|
+
|
10
|
+
If you use this plugin you let foreman-proxy provision/deprovision computer accounts and password and also distribute the password to a kickstart file.
|
11
|
+
|
12
|
+
It does the following:
|
13
|
+
|
14
|
+
* When hosts are created in foreman it will create a new computer account in active directory
|
15
|
+
and return the password to foreman so it can be used in the kickstart file.
|
16
|
+
|
17
|
+
* When hosts are rebuilt it will reset the computers account and return a new password to the kickstart file.
|
18
|
+
|
19
|
+
* When hosts are deleted in foreman it will delete the associated computer account in active directory.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
See How_to_Install_a_Smart-Proxy_Plugin for how to install Smart Proxy plugins.
|
23
|
+
|
24
|
+
Install this gem:
|
25
|
+
|
26
|
+
First clone this repo:
|
27
|
+
```
|
28
|
+
git clone https://github.com/martencassel/smart_proxy_realm_ad_plugin
|
29
|
+
```
|
30
|
+
|
31
|
+
Then run bundle and gem install.
|
32
|
+
|
33
|
+
```
|
34
|
+
cd smart_proxy_realm_ad_plugin
|
35
|
+
bundle install && gem build smart_proxy_realm_ad_plugin.gemspec \
|
36
|
+
&& sudo gem install smart_proxy_realm_ad_plugin-0.1.gem
|
37
|
+
|
38
|
+
```
|
39
|
+
|
40
|
+
Then add the depedencies to to smart-proxy bundler.d directory like below:
|
41
|
+
|
42
|
+
Edit 'bundler.d/Gemfile.local.rb' and set:
|
43
|
+
|
44
|
+
gem 'smart_proxy_realm_ad_plugin'
|
45
|
+
gem 'radcli'
|
46
|
+
gem 'rkerberos', '>= 0.1.1'
|
47
|
+
gem 'passgen'
|
48
|
+
|
49
|
+
## Configuration
|
50
|
+
|
51
|
+
Then enable this as a realm provider in foreman-proxy
|
52
|
+
|
53
|
+
To enable this realm provider, edit `/etc/foreman-proxy/settings.d/realm.yml` and set:
|
54
|
+
|
55
|
+
:enabled: true
|
56
|
+
|
57
|
+
:use_provider: realm_ad
|
58
|
+
|
59
|
+
## Testing
|
60
|
+
|
61
|
+
bundle exec rake test
|
62
|
+
|
63
|
+
## Install dependencies
|
64
|
+
|
65
|
+
Install the gem dependencies first:
|
66
|
+
|
67
|
+
1. rkerberos
|
68
|
+
2. radcli
|
69
|
+
|
70
|
+
### rkerberos
|
71
|
+
```
|
72
|
+
sudo gem install rkerberos
|
73
|
+
```
|
74
|
+
|
75
|
+
### radcli
|
76
|
+
|
77
|
+
#### radcli prereqs (ubuntu)
|
78
|
+
```
|
79
|
+
sudo apt-get install ruby gem ruby-dev
|
80
|
+
sudo gem install rake bundler rakecompiler rspec
|
81
|
+
sudo apt-get install automake autoconf xmlto xsltproc libkrb5-dev libldap2-dev libsasl2-dev
|
82
|
+
```
|
83
|
+
|
84
|
+
```
|
85
|
+
git clone https://github.com/martencassel/radcli
|
86
|
+
cd radcli
|
87
|
+
rake build
|
88
|
+
gem install pkg/radcli-0.1.0.gem
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
Fork and send a Pull Request. Thanks!
|
94
|
+
|
95
|
+
## Copyright
|
96
|
+
|
97
|
+
Copyright (c) 2016,2017 Mårten Cassel
|
98
|
+
|
99
|
+
This program is free software: you can redistribute it and/or modify
|
100
|
+
it under the terms of the GNU General Public License as published by
|
101
|
+
the Free Software Foundation, either version 3 of the License, or
|
102
|
+
(at your option) any later version.
|
103
|
+
|
104
|
+
This program is distributed in the hope that it will be useful,
|
105
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
106
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
107
|
+
GNU General Public License for more details.
|
108
|
+
|
109
|
+
You should have received a copy of the GNU General Public License
|
110
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
111
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
# Authentication for Kerberos-based Realms
|
3
|
+
:realm: EXAMPLE.COM
|
4
|
+
|
5
|
+
# Kerberos pricipal used to authenticate against Active Directory
|
6
|
+
:principal: realm-proxy@EXAMPLE.COM
|
7
|
+
|
8
|
+
# Path to the keytab used to authenticate against Active Directory
|
9
|
+
:keytab_path: /etc/foreman-proxy/realm_ad.keytab
|
10
|
+
|
11
|
+
# FQDN of the Domain Controller
|
12
|
+
:domain_controller: dc.example.com
|
13
|
+
|
14
|
+
# Optional: OU where the machine account shall be placed
|
15
|
+
#:ou: OU=Linux,OU=Servers,DC=example,DC=com
|
16
|
+
|
17
|
+
# Optional: Prefix for the computername
|
18
|
+
#:computername_prefix: ''
|
19
|
+
|
20
|
+
# Optional: Generate the computername by calculating the SHA256 hexdigest of the hostname
|
21
|
+
#:computername_hash: false
|
22
|
+
|
23
|
+
# Optional: use the fqdn of the host to generate the computername
|
24
|
+
#:computername_use_fqdn: false
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Proxy::AdRealm
|
2
|
+
class ConfigurationLoader
|
3
|
+
def load_classes
|
4
|
+
require 'smart_proxy_realm_ad/provider'
|
5
|
+
end
|
6
|
+
|
7
|
+
def load_dependency_injection_wirings(container_instance, settings)
|
8
|
+
container_instance.dependency :realm_provider_impl,
|
9
|
+
lambda {
|
10
|
+
::Proxy::AdRealm::Provider.new(
|
11
|
+
realm: settings[:realm],
|
12
|
+
keytab_path: settings[:keytab_path],
|
13
|
+
principal: settings[:principal],
|
14
|
+
domain_controller: settings[:domain_controller],
|
15
|
+
ou: settings[:ou],
|
16
|
+
computername_prefix: settings[:computername_prefix],
|
17
|
+
computername_hash: settings[:computername_hash],
|
18
|
+
computername_use_fqdn: settings[:computername_use_fqdn]
|
19
|
+
)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'smart_proxy_realm_ad/version'
|
2
|
+
|
3
|
+
module Proxy::AdRealm
|
4
|
+
class Plugin < Proxy::Provider
|
5
|
+
load_classes ::Proxy::AdRealm::ConfigurationLoader
|
6
|
+
load_dependency_injection_wirings ::Proxy::AdRealm::ConfigurationLoader
|
7
|
+
|
8
|
+
validate_presence :realm, :keytab_path, :principal, :domain_controller
|
9
|
+
|
10
|
+
plugin :realm_ad, ::Proxy::AdRealm::VERSION
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'proxy/kerberos'
|
2
|
+
require 'radcli'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Proxy::AdRealm
|
6
|
+
class Provider
|
7
|
+
include Proxy::Log
|
8
|
+
include Proxy::Util
|
9
|
+
include Proxy::Kerberos
|
10
|
+
|
11
|
+
attr_reader :realm, :keytab_path, :principal, :domain_controller, :domain, :ou, :computername_prefix, :computername_hash, :computername_use_fqdn
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@realm = options[:realm]
|
15
|
+
@keytab_path = options[:keytab_path]
|
16
|
+
@principal = options[:principal]
|
17
|
+
@domain_controller = options[:domain_controller]
|
18
|
+
@domain = options[:realm].downcase
|
19
|
+
@ou = options[:ou]
|
20
|
+
@computername_prefix = options[:computername_prefix]
|
21
|
+
@computername_hash = options.fetch(:computername_hash, false)
|
22
|
+
@computername_use_fqdn = options.fetch(:computername_use_fqdn, false)
|
23
|
+
logger.info 'Proxy::AdRealm: initialize...'
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_realm(realm)
|
27
|
+
raise Exception, "Unknown realm #{realm}" unless realm.casecmp(@realm).zero?
|
28
|
+
end
|
29
|
+
|
30
|
+
def find(_hostfqdn)
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(realm, hostfqdn, params)
|
35
|
+
logger.info "Proxy::AdRealm: create... #{realm}, #{hostfqdn}, #{params}"
|
36
|
+
check_realm(realm)
|
37
|
+
kinit_radcli_connect
|
38
|
+
|
39
|
+
password = generate_password
|
40
|
+
result = { randompassword: password }
|
41
|
+
|
42
|
+
computername = hostfqdn_to_computername(hostfqdn)
|
43
|
+
|
44
|
+
if params[:rebuild] == 'true'
|
45
|
+
radcli_password(computername, password)
|
46
|
+
else
|
47
|
+
radcli_join(hostfqdn, computername, password)
|
48
|
+
end
|
49
|
+
|
50
|
+
JSON.pretty_generate(result)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(realm, hostfqdn)
|
54
|
+
logger.info "Proxy::AdRealm: delete... #{realm}, #{hostfqdn}"
|
55
|
+
kinit_radcli_connect
|
56
|
+
check_realm(realm)
|
57
|
+
computername = hostfqdn_to_computername(hostfqdn)
|
58
|
+
radcli_delete(computername)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def hostfqdn_to_computername(hostfqdn)
|
64
|
+
computername = hostfqdn
|
65
|
+
|
66
|
+
# strip the domain from the host
|
67
|
+
computername = computername.split('.').first unless computername_use_fqdn
|
68
|
+
|
69
|
+
# generate the SHA256 hexdigest from the computername
|
70
|
+
computername = Digest::SHA256.hexdigest(computername) if computername_hash
|
71
|
+
|
72
|
+
# apply prefix if it has not already been applied
|
73
|
+
computername = computername_prefix + computername if apply_computername_prefix?(computername)
|
74
|
+
|
75
|
+
# limit length to 15 characters and upcase the computername
|
76
|
+
# see https://support.microsoft.com/en-us/kb/909264
|
77
|
+
computername[0, 15].upcase
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply_computername_prefix?(computername)
|
81
|
+
!computername_prefix.nil? && !computername_prefix.empty? && (computername_hash || !computername[0, computername_prefix.size].casecmp(computername_prefix).zero?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def kinit_radcli_connect
|
85
|
+
init_krb5_ccache(@keytab_path, @principal)
|
86
|
+
@adconn = radcli_connect
|
87
|
+
end
|
88
|
+
|
89
|
+
def radcli_connect
|
90
|
+
# Connect to active directory
|
91
|
+
conn = Adcli::AdConn.new(@domain)
|
92
|
+
conn.set_domain_realm(@realm)
|
93
|
+
conn.set_domain_controller(@domain_controller)
|
94
|
+
conn.set_login_ccache_name('')
|
95
|
+
conn.connect
|
96
|
+
conn
|
97
|
+
end
|
98
|
+
|
99
|
+
def radcli_join(hostfqdn, computername, password)
|
100
|
+
# Join computer
|
101
|
+
enroll = Adcli::AdEnroll.new(@adconn)
|
102
|
+
enroll.set_computer_name(computername)
|
103
|
+
enroll.set_host_fqdn(hostfqdn)
|
104
|
+
enroll.set_domain_ou(@ou) if @ou
|
105
|
+
enroll.set_computer_password(password)
|
106
|
+
enroll.join
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_password
|
110
|
+
characters = ('A'..'Z').to_a + ('a'..'z').to_a + (0..9).to_a
|
111
|
+
Array.new(20) { characters.sample }.join
|
112
|
+
end
|
113
|
+
|
114
|
+
def radcli_password(computername, password)
|
115
|
+
# Reset a computer's password
|
116
|
+
enroll = Adcli::AdEnroll.new(@adconn)
|
117
|
+
enroll.set_computer_name(computername)
|
118
|
+
enroll.set_domain_ou(@ou) if @ou
|
119
|
+
enroll.set_computer_password(password)
|
120
|
+
enroll.password
|
121
|
+
end
|
122
|
+
|
123
|
+
def radcli_delete(computername)
|
124
|
+
# Delete a computer's account
|
125
|
+
enroll = Adcli::AdEnroll.new(@adconn)
|
126
|
+
enroll.set_computer_name(computername)
|
127
|
+
enroll.set_domain_ou(@ou) if @ou
|
128
|
+
enroll.delete
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test_helper'
|
3
|
+
require 'smart_proxy_realm_ad/provider'
|
4
|
+
require 'radcli'
|
5
|
+
|
6
|
+
class RealmAdTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@realm = 'test_realm'
|
9
|
+
@provider = Proxy::AdRealm::Provider.new(
|
10
|
+
realm: 'example.com',
|
11
|
+
keytab_path: 'keytab_path',
|
12
|
+
principal: 'principal',
|
13
|
+
domain_controller: 'domain-controller',
|
14
|
+
ou: nil,
|
15
|
+
computername_prefix: nil,
|
16
|
+
computername_hash: false,
|
17
|
+
computername_use_fqdn: false
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_create_host
|
22
|
+
hostname = 'host.example.com'
|
23
|
+
computername = 'HOST'
|
24
|
+
params = {
|
25
|
+
rebuild: 'false'
|
26
|
+
}
|
27
|
+
@provider.expects(:check_realm).with(@realm)
|
28
|
+
@provider.expects(:kinit_radcli_connect)
|
29
|
+
@provider.expects(:generate_password).returns('password')
|
30
|
+
@provider.expects(:radcli_join).with(hostname, computername, 'password')
|
31
|
+
@provider.create(@realm, hostname, params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_create_host_generates_password
|
35
|
+
hostname = 'host.example.com'
|
36
|
+
params = {
|
37
|
+
rebuild: 'false'
|
38
|
+
}
|
39
|
+
@provider.expects(:check_realm).with(@realm)
|
40
|
+
@provider.expects(:kinit_radcli_connect)
|
41
|
+
@provider.expects(:radcli_join)
|
42
|
+
response = JSON.parse(@provider.create(@realm, hostname, params))
|
43
|
+
assert_kind_of String, response['randompassword']
|
44
|
+
assert_equal 20, response['randompassword'].size
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_create_host_prefixes_computername
|
48
|
+
hostname = 'host.example.com'
|
49
|
+
computername = 'ORG-HOST'
|
50
|
+
params = {
|
51
|
+
rebuild: 'false'
|
52
|
+
}
|
53
|
+
@provider.stubs(:computername_prefix).returns('ORG-')
|
54
|
+
@provider.expects(:check_realm).with(@realm)
|
55
|
+
@provider.expects(:kinit_radcli_connect)
|
56
|
+
@provider.expects(:generate_password).returns('password')
|
57
|
+
@provider.expects(:radcli_join).with(hostname, computername, 'password')
|
58
|
+
@provider.create(@realm, hostname, params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_create_host_limits_computername_to_15_characters
|
62
|
+
hostname = 'superlonghostname.example.com'
|
63
|
+
computername = 'SUPERLONGHOSTNA'
|
64
|
+
params = {
|
65
|
+
rebuild: 'false'
|
66
|
+
}
|
67
|
+
@provider.expects(:check_realm).with(@realm)
|
68
|
+
@provider.expects(:kinit_radcli_connect)
|
69
|
+
@provider.expects(:generate_password).returns('password')
|
70
|
+
@provider.expects(:radcli_join).with(hostname, computername, 'password')
|
71
|
+
@provider.create(@realm, hostname, params)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_create_host_applies_sha256_to_computername
|
75
|
+
hostname = 'host.example.com'
|
76
|
+
computername = '4740AE6347B0172'
|
77
|
+
params = {
|
78
|
+
rebuild: 'false'
|
79
|
+
}
|
80
|
+
@provider.stubs(:computername_hash).returns(true)
|
81
|
+
@provider.expects(:check_realm).with(@realm)
|
82
|
+
@provider.expects(:kinit_radcli_connect)
|
83
|
+
@provider.expects(:generate_password).returns('password')
|
84
|
+
@provider.expects(:radcli_join).with(hostname, computername, 'password')
|
85
|
+
@provider.create(@realm, hostname, params)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_create_with_unrecognized_realm_raises_exception
|
89
|
+
assert_raises(Exception) { @provider.create('unknown_realm', 'a_host', {}) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_create_rebuild
|
93
|
+
hostname = 'host.example.com'
|
94
|
+
params = {}
|
95
|
+
params[:rebuild] = 'true'
|
96
|
+
@provider.expects(:check_realm).with(@realm)
|
97
|
+
@provider.expects(:kinit_radcli_connect)
|
98
|
+
@provider.expects(:radcli_password)
|
99
|
+
response = JSON.parse(@provider.create(@realm, hostname, params))
|
100
|
+
assert_kind_of String, response['randompassword']
|
101
|
+
assert_equal 20, response['randompassword'].size
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_rebuild_with_unrecognized_realm_raises_exception
|
105
|
+
params = {}
|
106
|
+
params[:rebuild] = 'true'
|
107
|
+
assert_raises(Exception) { @provider.create('unknown_realm', 'a_host', params) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_find
|
111
|
+
assert_true @provider.find('a_host_fqdn')
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_delete
|
115
|
+
@provider.expects(:check_realm).with(@realm)
|
116
|
+
@provider.expects(:kinit_radcli_connect)
|
117
|
+
@provider.expects(:radcli_delete)
|
118
|
+
@provider.delete(@realm, 'a_host')
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_delete_unrecognized_realm_raises_exception
|
122
|
+
@provider.expects(:kinit_radcli_connect)
|
123
|
+
assert_raises(Exception) { @provider.delete('unkown_realm', 'a_host') }
|
124
|
+
end
|
125
|
+
end
|