macadmin 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +163 -0
- data/Rakefile +23 -0
- data/ext/macadmin/password/crypto.c +89 -0
- data/ext/macadmin/password/extconf.rb +3 -0
- data/lib/macadmin.rb +22 -0
- data/lib/macadmin/common.rb +80 -0
- data/lib/macadmin/dslocal.rb +252 -0
- data/lib/macadmin/dslocal/computer.rb +22 -0
- data/lib/macadmin/dslocal/computergroup.rb +19 -0
- data/lib/macadmin/dslocal/dslocalnode.rb +281 -0
- data/lib/macadmin/dslocal/group.rb +82 -0
- data/lib/macadmin/dslocal/user.rb +113 -0
- data/lib/macadmin/mcx.rb +227 -0
- data/lib/macadmin/password.rb +72 -0
- data/lib/macadmin/shadowhash.rb +297 -0
- data/lib/macadmin/version.rb +8 -0
- data/macadmin.gemspec +35 -0
- data/spec/common_spec.rb +49 -0
- data/spec/computer_spec.rb +133 -0
- data/spec/computergroup_spec.rb +274 -0
- data/spec/dslocal_spec.rb +179 -0
- data/spec/dslocalnode_spec.rb +343 -0
- data/spec/group_spec.rb +275 -0
- data/spec/macadmin_spec.rb +13 -0
- data/spec/mcx_spec.rb +145 -0
- data/spec/password_spec.rb +40 -0
- data/spec/shadowhash_spec.rb +309 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/user_spec.rb +248 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a1103e52f907edbf5e1f175278e7eaecb7f5be21
|
4
|
+
data.tar.gz: db158590675a6499a351b7ca06c77d0d60078ff8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 898ced3234ca7f09c0271bab0ebcce352c02a01c015349aabcf815a5463b82833e4dd7a94c1e5475b2a65f3f06fc1d937b02a96463df4eae30e46e9a144d7b14
|
7
|
+
data.tar.gz: c3e4d8b825d5ec08a684edf1a59c5f40a107b8cb3bcc8bf5567729249bc436e82c064e7c16704de8dd6d84cadcb7caf835e043b43ad35f7ab0ed285ca22bbbf5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brian Warsing
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# MacAdmin
|
2
|
+
|
3
|
+
Gem to assist in performing common systems administration tasks in OS X
|
4
|
+
|
5
|
+
## Version: 0.0.4
|
6
|
+
|
7
|
+
## About
|
8
|
+
|
9
|
+
MacAdmin endeavors to provide an OO programming interface for constituent OS X system resources. It's comprised of classes with the ability to parse Apple Property Lists (CFPropertyList) and manipulate them as native Ruby objects. The classes work directly with the Property Lists used to abstract OS X system resources -- users, groups, computers, computergroups, etc. -- bypassing the common utilities and APIs normally reserved for this kind of work.
|
10
|
+
|
11
|
+
This approach has trade-offs, but it does result in a very powerful and simple model for managing these resources (See Notes).
|
12
|
+
|
13
|
+
#### Notes:
|
14
|
+
|
15
|
+
Before forking/cloning/using/testing/etc MacAdmin, please read the license (LICENSE.txt).
|
16
|
+
|
17
|
+
One important trade-off worth mentioning when using this gem is that you must have root priviledge in order to access (read) any resources in the DSLocal domains or similarily protected directories and files. This is different from using utils like `dscl`, but not unlike using `defaults`. Naturally, as with any of these methods, you must also be root in order to make any changes. The code examples below will require root access when performing create operations.
|
18
|
+
|
19
|
+
Another important condition to mention is that it will often be necessary to restart OS X's directory service in order to see the changes you've made to any of the affected plists. This can be bothersome and susceptible to race conditions, but in general, it's a manageable issue.
|
20
|
+
|
21
|
+
## Requirements
|
22
|
+
|
23
|
+
- Mac OS X 10.5 and up
|
24
|
+
- Xcode Command Line Tools
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
### Simple:
|
29
|
+
|
30
|
+
RubyGems should install any dependencies:
|
31
|
+
|
32
|
+
$ sudo gem install macadmin
|
33
|
+
|
34
|
+
### Manual:
|
35
|
+
|
36
|
+
Install the bundler gem:
|
37
|
+
|
38
|
+
$ sudo gem install bundler
|
39
|
+
|
40
|
+
Clone this repo:
|
41
|
+
|
42
|
+
$ cd ~/Downloads
|
43
|
+
$ git clone http://github.com/dayglojesus/macadmin.git
|
44
|
+
|
45
|
+
Install the gem dependencies:
|
46
|
+
|
47
|
+
$ cd macadmin
|
48
|
+
$ sudo bundle install
|
49
|
+
|
50
|
+
Run the tests:
|
51
|
+
|
52
|
+
$ rake
|
53
|
+
|
54
|
+
Install the gem:
|
55
|
+
|
56
|
+
$ sudo rake install
|
57
|
+
|
58
|
+
Test the installation:
|
59
|
+
|
60
|
+
##### Note the path parameter to #create -- use this for testing resource creation in arbitrary directories. Also works for #destroy.
|
61
|
+
|
62
|
+
$ cd ~/Downloads
|
63
|
+
$ irb -r 'macadmin'
|
64
|
+
>> foobar = User.new 'foobar'
|
65
|
+
=> {"passwd"=>["********"], "gid"=>["20"], "uid"=>["501"], "shell"=>["/bin/bash"], "name"=>["foobar"], "realname"=>["foobar"], "generateduid"=>["4871DD7C-5C55-47DB-8A7B-B38CBD6DA5A9"], "comment"=>[""], "home"=>["/Users/foobar"]}
|
66
|
+
>> foobar.exists?
|
67
|
+
=> false
|
68
|
+
>> foobar.create "./foobar.plist"
|
69
|
+
>> foobar.destroy "./foobar.plist"
|
70
|
+
>> exit
|
71
|
+
|
72
|
+
## Usage
|
73
|
+
|
74
|
+
Load the gems:
|
75
|
+
|
76
|
+
require 'rubygems'
|
77
|
+
require 'macadmin'
|
78
|
+
|
79
|
+
Create a new node:
|
80
|
+
|
81
|
+
# Here's something cool:
|
82
|
+
# DSLocalNode will automatically add this custom node to OpenDirectory sandbox when running on 10.8 and up
|
83
|
+
my_custom_node = DSLocalNode.new 'MCX'
|
84
|
+
my_custom_node.create_and_activate
|
85
|
+
|
86
|
+
Create a computer record on that node:
|
87
|
+
|
88
|
+
computer = Computer.new :name => `hostname -s`.chomp, :node => 'MCX'
|
89
|
+
computer.create
|
90
|
+
|
91
|
+
Create a computer group, add the computer record as a member, and apply some policy (on that node):
|
92
|
+
|
93
|
+
# Here's a bnuch of policy written as XML, but the mcximport method can also take a file path parameter and load it that way
|
94
|
+
raw_xml_policy = <<-RAW_XML_CONTENT
|
95
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
96
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
97
|
+
<plist version="1.0">
|
98
|
+
<dict>
|
99
|
+
<key>com.apple.SoftwareUpdate</key>
|
100
|
+
<dict>
|
101
|
+
<key>CatalogURL</key>
|
102
|
+
<dict>
|
103
|
+
<key>state</key>
|
104
|
+
<string>always</string>
|
105
|
+
<key>value</key>
|
106
|
+
<string>http://foo.bar.com/reposado/html/content/catalogs/index.sucatalog</string>
|
107
|
+
</dict>
|
108
|
+
</dict>
|
109
|
+
<key>com.apple.screensaver</key>
|
110
|
+
<dict>
|
111
|
+
<key>askForPassword</key>
|
112
|
+
<dict>
|
113
|
+
<key>state</key>
|
114
|
+
<string>once</string>
|
115
|
+
<key>value</key>
|
116
|
+
<integer>1</integer>
|
117
|
+
</dict>
|
118
|
+
</dict>
|
119
|
+
</dict>
|
120
|
+
</plist>
|
121
|
+
RAW_XML_CONTENT
|
122
|
+
|
123
|
+
computer_group = ComputerGroup.new :name => 'mcx', :realname => 'MCX', :node => 'MCX'
|
124
|
+
computer_group.add_user `hostname -s`.chomp
|
125
|
+
computer_group.mcximport raw_xml_policy
|
126
|
+
computer_group.create
|
127
|
+
|
128
|
+
Create an administrator for your new node:
|
129
|
+
|
130
|
+
# Generate a platform appropriate password from a plaintext string
|
131
|
+
password = Password.apropos "secret_passphrase"
|
132
|
+
administrator = User.new :name => 'mcxadmin', :password => password, :gid => 80, :node => 'MCX'
|
133
|
+
administrator.create
|
134
|
+
|
135
|
+
Restart the directory services to seal the deal:
|
136
|
+
|
137
|
+
restart_directoryservice
|
138
|
+
|
139
|
+
Explore a bit...
|
140
|
+
|
141
|
+
require 'pp'
|
142
|
+
|
143
|
+
# Show the User object
|
144
|
+
admin = User.new :name => 'mcxadmin', :node => 'MCX'
|
145
|
+
pp admin.record if admin.exists?
|
146
|
+
|
147
|
+
# Show the MCX policy attached to the ComputerGroup object
|
148
|
+
comp_grp = ComputerGroup.new :name => 'mcx', :node => 'MCX'
|
149
|
+
puts "Does this local computer group have MCX policy?"
|
150
|
+
puts comp_grp.has_mcx? ? "Sweet!" : "Bummer..."
|
151
|
+
puts comp_grp.mcxexport
|
152
|
+
|
153
|
+
Tear it down...
|
154
|
+
|
155
|
+
# This will take down the entire node we just created and populated: user, computers, computer groups, etc.
|
156
|
+
mcx_node = DSLocalNode.new 'MCX'
|
157
|
+
mcx_node.destroy_and_deactivate
|
158
|
+
|
159
|
+
restart_directoryservice
|
160
|
+
|
161
|
+
## Acknowledgments
|
162
|
+
|
163
|
+
This gem would not be possible without [ckuse's CFPropertyList](https://github.com/ckruse/CFPropertyList). Thanks, Christian.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "macadmin/common"
|
4
|
+
|
5
|
+
# Only build the extension for Mountian Lion or better
|
6
|
+
if MacAdmin::Common::MAC_OS_X_PRODUCT_VERSION > 10.7
|
7
|
+
|
8
|
+
require "rake/extensiontask"
|
9
|
+
|
10
|
+
Rake::ExtensionTask.new "crypto" do |ext|
|
11
|
+
ext.lib_dir = 'lib/macadmin/password'
|
12
|
+
ext.name = 'crypto'
|
13
|
+
ext.ext_dir = 'ext/macadmin/password'
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::Task[:spec].prerequisites << :compile
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec::Core::RakeTask.new
|
21
|
+
|
22
|
+
task :default => :spec
|
23
|
+
task :test => :spec
|
@@ -0,0 +1,89 @@
|
|
1
|
+
//
|
2
|
+
// crypto.c
|
3
|
+
// MacAdmin::Password::Crypto
|
4
|
+
// - Ruby C extension built for fast PBKDF2 calculation
|
5
|
+
//
|
6
|
+
// Created by Brian Warsing on 2013-10-07.
|
7
|
+
// Copyright (c) 2013 Simon Fraser University. All rights reserved.
|
8
|
+
//
|
9
|
+
|
10
|
+
#include <stdio.h>
|
11
|
+
#include <stdint.h>
|
12
|
+
#include <CommonCrypto/CommonCrypto.h>
|
13
|
+
#include "ruby.h"
|
14
|
+
|
15
|
+
VALUE MacAdmin = Qnil;
|
16
|
+
VALUE Password = Qnil;
|
17
|
+
|
18
|
+
// Prototypes
|
19
|
+
void Init_crypto();
|
20
|
+
static VALUE salted_sha512_pbkdf2_from_string(VALUE self, VALUE input);
|
21
|
+
void to_hex( uint8_t *dest, const uint8_t *text, size_t text_size );
|
22
|
+
|
23
|
+
// Convert an ASCII char array to hexidecimal representation
|
24
|
+
void to_hex( uint8_t *dest, const uint8_t *text, size_t text_size )
|
25
|
+
{
|
26
|
+
int i;
|
27
|
+
for(i = 0; i < text_size; i++)
|
28
|
+
sprintf((char*)dest+i*2, "%02x", text[i]);
|
29
|
+
}
|
30
|
+
|
31
|
+
// Ruby Init
|
32
|
+
void Init_crypto() {
|
33
|
+
MacAdmin = rb_define_module("MacAdmin");
|
34
|
+
Password = rb_define_module_under(MacAdmin, "Password");
|
35
|
+
rb_define_singleton_method(Password, "salted_sha512_pbkdf2_from_string", salted_sha512_pbkdf2_from_string, 1);
|
36
|
+
}
|
37
|
+
|
38
|
+
// salted_sha512_pbkdf2_from_string
|
39
|
+
// - single param: Ruby String
|
40
|
+
// - returns Ruby Hash with 3 keys
|
41
|
+
// http://blog.securemacprogramming.com/2012/07/password-checking-with-commoncrypto/
|
42
|
+
static VALUE salted_sha512_pbkdf2_from_string(VALUE self, VALUE input) {
|
43
|
+
|
44
|
+
VALUE str = StringValue(input);
|
45
|
+
char *password = RSTRING_PTR(str); // may be null
|
46
|
+
size_t password_size = RSTRING_LEN(str);
|
47
|
+
int salt_len = 32;
|
48
|
+
|
49
|
+
// Calc how many iterations
|
50
|
+
const u_int32_t interval = arc4random_uniform(100) + 100;
|
51
|
+
int iterations = CCCalibratePBKDF(kCCPBKDF2,
|
52
|
+
password_size,
|
53
|
+
salt_len,
|
54
|
+
kCCPRFHmacAlgSHA512,
|
55
|
+
kCCKeySizeMaxRC2,
|
56
|
+
interval);
|
57
|
+
|
58
|
+
// Generate a salt
|
59
|
+
int i;
|
60
|
+
uint8_t salt[salt_len];
|
61
|
+
for (i = 0; i < salt_len; i++)
|
62
|
+
salt[i] = (unsigned char)arc4random();
|
63
|
+
|
64
|
+
// Generate HMAC
|
65
|
+
uint8_t key[kCCKeySizeMaxRC2] = {0};
|
66
|
+
int result = CCKeyDerivationPBKDF(kCCPBKDF2,
|
67
|
+
password,
|
68
|
+
password_size,
|
69
|
+
salt,
|
70
|
+
salt_len,
|
71
|
+
kCCPRFHmacAlgSHA512,
|
72
|
+
iterations,
|
73
|
+
key,
|
74
|
+
kCCKeySizeMaxRC2);
|
75
|
+
|
76
|
+
uint8_t *entropy[kCCKeySizeMaxRC2];
|
77
|
+
to_hex(entropy, key, kCCKeySizeMaxRC2);
|
78
|
+
|
79
|
+
uint8_t *salt_hex[salt_len];
|
80
|
+
to_hex(salt_hex, salt, salt_len);
|
81
|
+
|
82
|
+
VALUE dict;
|
83
|
+
dict = rb_hash_new();
|
84
|
+
rb_hash_aset(dict, ID2SYM(rb_intern(("entropy"))), rb_str_new2((char *)entropy));
|
85
|
+
rb_hash_aset(dict, ID2SYM(rb_intern(("salt"))), rb_str_new2((char *)salt_hex));
|
86
|
+
rb_hash_aset(dict, ID2SYM(rb_intern(("iterations"))), INT2NUM(iterations));
|
87
|
+
|
88
|
+
return dict;
|
89
|
+
}
|
data/lib/macadmin.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# macadmin gem
|
2
|
+
# Sat Aug 11 16:14:56 PDT 2012
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cgi'
|
5
|
+
require 'time'
|
6
|
+
require 'delegate'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'cfpropertylist'
|
9
|
+
require 'macadmin/version'
|
10
|
+
require 'macadmin/common'
|
11
|
+
require 'macadmin/mcx'
|
12
|
+
require 'macadmin/password'
|
13
|
+
require 'macadmin/shadowhash'
|
14
|
+
require 'macadmin/dslocal'
|
15
|
+
require 'macadmin/dslocal/user'
|
16
|
+
require 'macadmin/dslocal/group'
|
17
|
+
require 'macadmin/dslocal/computer'
|
18
|
+
require 'macadmin/dslocal/computergroup'
|
19
|
+
require 'macadmin/dslocal/dslocalnode'
|
20
|
+
|
21
|
+
include MacAdmin
|
22
|
+
include MacAdmin::Common
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module MacAdmin
|
2
|
+
|
3
|
+
# Common
|
4
|
+
# - module comtaining classes and methods common to macadmin
|
5
|
+
module Common
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Mac OS X major version number
|
10
|
+
MAC_OS_X_PRODUCT_VERSION = `/usr/bin/sw_vers`.split("\n")[1].split("\t").last.to_f
|
11
|
+
|
12
|
+
# Class for creating and checking UUID strings
|
13
|
+
class UUID < String
|
14
|
+
|
15
|
+
# 897A6343-628F-4964-80F1-C86D0FFA3F91
|
16
|
+
UUID_REGEX = '([A-Z0-9]{8})-([A-Z0-9]{4}-){3}([A-Z0-9]{12})'
|
17
|
+
|
18
|
+
# Create a new UUID string
|
19
|
+
def initialize
|
20
|
+
uuid = %x{/usr/bin/uuidgen}.chomp
|
21
|
+
super(uuid)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Class methods
|
25
|
+
class << self
|
26
|
+
|
27
|
+
# Matches any string containing a UUID
|
28
|
+
# - returns Boolean
|
29
|
+
def match(string, options = nil)
|
30
|
+
string =~ Regexp.new(UUID_REGEX, options) ? $& : false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validates the format of a UUID string
|
34
|
+
# - only true is the entire string is a UUID match
|
35
|
+
# - returns Boolean
|
36
|
+
def valid?(uuid, options = nil)
|
37
|
+
strictlyuuid = '^' + UUID_REGEX + '$'
|
38
|
+
uuid =~ Regexp.new(strictlyuuid, options) ? true : false
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the primary ethernet's MAC address
|
46
|
+
# - returns String
|
47
|
+
def get_primary_mac_address
|
48
|
+
raw = %x{/sbin/ifconfig en0}
|
49
|
+
raw.split("\n").grep(/ether/).first.split.last
|
50
|
+
end
|
51
|
+
|
52
|
+
# Load a PropertyList file
|
53
|
+
# - convenience method
|
54
|
+
# - single parameter is a path to the ProperyList file
|
55
|
+
# - returns a Hash is if can parse the file
|
56
|
+
# - returns nil if it cannot parse the file
|
57
|
+
def load_plist(file)
|
58
|
+
return nil unless File.exists? file
|
59
|
+
plist = CFPropertyList::List.new(:file => file)
|
60
|
+
CFPropertyList.native_types(plist.value)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Restart the local directory service daemon
|
64
|
+
# - single param wait (Integer) measured in seconds
|
65
|
+
# - default wait is 10 seconds
|
66
|
+
def restart_directoryservice(wait=10)
|
67
|
+
if MAC_OS_X_PRODUCT_VERSION > 10.6
|
68
|
+
system('/usr/bin/killall opendirectoryd')
|
69
|
+
else
|
70
|
+
system('/usr/bin/killall DirectoryService')
|
71
|
+
end
|
72
|
+
sleep wait
|
73
|
+
end
|
74
|
+
|
75
|
+
# Alias for convenience
|
76
|
+
alias :restart_opendirectoryd :restart_directoryservice
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|