forked from kai-h/vpn-mobileconfig-generator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
#!/usr/local/munki/python | ||
|
||
# Kai Howells | ||
# [email protected] | ||
# | ||
|
||
import os | ||
import sys | ||
import uuid | ||
import optparse | ||
from munkilib import FoundationPlist | ||
|
||
|
||
def build_plist(sharedSecret, username, password, company, server): | ||
|
||
uuidOne = str(uuid.uuid4()) | ||
uuidTwo = str(uuid.uuid4()) | ||
|
||
plist = dict( | ||
PayloadContent = [ dict( | ||
DisconnectOnIdle = 0, | ||
IPSec = dict( | ||
AuthenticationMethod = "SharedSecret", | ||
OnDemandEnabled = 0, | ||
PromptForVPNPIN = False, | ||
# this somehow needs to be a data object, not a string | ||
# so it's masked as base64 in the plist... | ||
# but I don't know how to use FoundationPlist to create | ||
# a data object, so it's a plain string at the moment | ||
SharedSecret = sharedSecret | ||
), | ||
IPv4 = dict( | ||
OverridePrimary = False, | ||
), | ||
PPP = dict( | ||
AuthName = username, | ||
AuthPassword = password, | ||
AuthenticationMethod = "Password", | ||
CommRemoteAddress = server, | ||
OnDemandEnabled = 0, | ||
), | ||
PayloadDisplayName = "VPN (" + company +")", | ||
PayloadEnabled = True, | ||
PayloadIdentifier = "com.apple.mdm." + company.lower() + ".private." + uuidOne + ".alacarte.vpn." + uuidTwo, | ||
PayloadType = "com.apple.vpn.managed", | ||
PayloadUUID = uuidTwo, | ||
PayloadVersion = 1, | ||
Proxies = dict( | ||
), | ||
UserDefinedName = company + " VPN", | ||
VPNType = "L2TP", | ||
) | ||
], | ||
PayloadDisplayName = company + " VPN", | ||
PayloadIdentifier = "com.apple.mdm." + company.lower().replace(" ", "") + ".private." + uuidOne + ".alacarte", | ||
PayloadOrganization = company, | ||
PayloadRemovalDisallowed = False, | ||
PayloadScope = "User", | ||
PayloadType = "Configuratrion", | ||
PayloadUUID = uuidOne, | ||
PayloadVersion = 1, | ||
) | ||
return plist | ||
|
||
def write_plist(plist,filename): | ||
FoundationPlist.writePlist(plist, filename) | ||
|
||
def main(): | ||
"""Main routine""" | ||
|
||
usage = """usage: %prog [options] [/path/to/profile.mobileconfig] | ||
Creates a configuration profile, profile.mobileconfig | ||
To configure a L2TP VPN on a macOS machine without needing | ||
to have Profile Manager running on a macOS Server just to | ||
build some basic configuration profiles. | ||
""" | ||
|
||
parser = optparse.OptionParser(usage=usage) | ||
|
||
parser.add_option('--username', '-u', | ||
help='The username for the L2TP VPN Server.') | ||
parser.add_option('--password', '-p', | ||
help='The password for the L2TP VPN Server.') | ||
parser.add_option('--secret', '-s', | ||
help='The shared secret for the L2TP VPN Server.') | ||
parser.add_option('--company', '-c', | ||
help='The company name for the generated Configuration Profile.') | ||
parser.add_option('--vpn', '-v', | ||
help='The IP Address or hostname of the VPN Endpoint.') | ||
|
||
if len(sys.argv) == 1: | ||
parser.print_usage() | ||
exit(0) | ||
|
||
options, arguments = parser.parse_args() | ||
|
||
if not options.username or not options.password or not options.secret or not options.company or not options.vpn: | ||
parser.print_help() | ||
exit(0) | ||
|
||
sharedSecret = options.secret | ||
username = options.username | ||
password = options.password | ||
company = options.company | ||
server = options.vpn | ||
|
||
mobileConfig = None | ||
if arguments: | ||
mobileConfig = arguments[0] | ||
else: | ||
mobileConfig = os.getcwd() +"/" + company + " VPN for " + username + ".mobileconfig" | ||
|
||
print ("Created Configuration Profile: " + mobileConfig) | ||
|
||
plist = build_plist(sharedSecret, username, password, company, server) | ||
write_plist(plist,mobileConfig) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# encoding: utf-8 | ||
# | ||
# Copyright 2009-2020 Greg Neagle. | ||
# | ||
# 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 | ||
# | ||
# https://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. | ||
"""FoundationPlist.py -- a tool to generate and parse OS X .plist files. | ||
This is intended as a drop-in replacement for Python's included plistlib, | ||
with a few caveats: | ||
- readPlist() and writePlist() operate only on a filepath, | ||
not a file object. | ||
- there is no support for the deprecated functions: | ||
readPlistFromResource() | ||
writePlistToResource() | ||
- there is no support for the deprecated Plist class. | ||
The Property List (.plist) file format is a simple XML pickle supporting | ||
basic object types, like dictionaries, lists, numbers and strings. | ||
Usually the top level object is a dictionary. | ||
To write out a plist file, use the writePlist(rootObject, filepath) | ||
function. 'rootObject' is the top level object, 'filepath' is a | ||
filename. | ||
To parse a plist from a file, use the readPlist(filepath) function, | ||
with a file name. It returns the top level object (again, usually a | ||
dictionary). | ||
To work with plist data in strings, you can use readPlistFromString() | ||
and writePlistToString(). | ||
""" | ||
from __future__ import absolute_import, print_function | ||
|
||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus | ||
# No name 'Foo' in module 'Bar' warnings. Disable them. | ||
# pylint: disable=E0611 | ||
from Foundation import NSData | ||
from Foundation import NSPropertyListSerialization | ||
from Foundation import NSPropertyListMutableContainers | ||
from Foundation import NSPropertyListXMLFormat_v1_0 | ||
# pylint: enable=E0611 | ||
|
||
# Disable PyLint complaining about 'invalid' camelCase names | ||
# pylint: disable=C0103 | ||
|
||
|
||
class FoundationPlistException(Exception): | ||
"""Basic exception for plist errors""" | ||
pass | ||
|
||
class NSPropertyListSerializationException(FoundationPlistException): | ||
"""Read/parse error for plists""" | ||
pass | ||
|
||
class NSPropertyListWriteException(FoundationPlistException): | ||
"""Write error for plists""" | ||
pass | ||
|
||
def readPlist(filepath): | ||
""" | ||
Read a .plist file from filepath. Return the unpacked root object | ||
(which is usually a dictionary). | ||
""" | ||
plistData = NSData.dataWithContentsOfFile_(filepath) | ||
dataObject, dummy_plistFormat, error = ( | ||
NSPropertyListSerialization. | ||
propertyListFromData_mutabilityOption_format_errorDescription_( | ||
plistData, NSPropertyListMutableContainers, None, None)) | ||
if dataObject is None: | ||
if error: | ||
error = error.encode('ascii', 'ignore') | ||
else: | ||
error = "Unknown error" | ||
errmsg = "%s in file %s" % (error, filepath) | ||
raise NSPropertyListSerializationException(errmsg) | ||
else: | ||
return dataObject | ||
|
||
|
||
def readPlistFromString(data): | ||
'''Read a plist data from a (byte)string. Return the root object.''' | ||
plistData = NSData.dataWithBytes_length_(data, len(data)) | ||
if not plistData: | ||
raise NSPropertyListSerializationException( | ||
"Could not convert string to NSData") | ||
dataObject, dummy_plistFormat, error = ( | ||
NSPropertyListSerialization. | ||
propertyListFromData_mutabilityOption_format_errorDescription_( | ||
plistData, NSPropertyListMutableContainers, None, None)) | ||
if dataObject is None: | ||
if error: | ||
error = error.encode('ascii', 'ignore') | ||
else: | ||
error = "Unknown error" | ||
raise NSPropertyListSerializationException(error) | ||
else: | ||
return dataObject | ||
|
||
|
||
def writePlist(dataObject, filepath): | ||
''' | ||
Write 'rootObject' as a plist to filepath. | ||
''' | ||
plistData, error = ( | ||
NSPropertyListSerialization. | ||
dataFromPropertyList_format_errorDescription_( | ||
dataObject, NSPropertyListXMLFormat_v1_0, None)) | ||
if plistData is None: | ||
if error: | ||
error = error.encode('ascii', 'ignore') | ||
else: | ||
error = "Unknown error" | ||
raise NSPropertyListSerializationException(error) | ||
else: | ||
if plistData.writeToFile_atomically_(filepath, True): | ||
return | ||
else: | ||
raise NSPropertyListWriteException( | ||
"Failed to write plist data to %s" % filepath) | ||
|
||
|
||
def writePlistToString(rootObject): | ||
'''Return 'rootObject' as a plist-formatted (byte)string.''' | ||
plistData, error = ( | ||
NSPropertyListSerialization. | ||
dataFromPropertyList_format_errorDescription_( | ||
rootObject, NSPropertyListXMLFormat_v1_0, None)) | ||
if plistData is None: | ||
if error: | ||
error = error.encode('ascii', 'ignore') | ||
else: | ||
error = "Unknown error" | ||
raise NSPropertyListSerializationException(error) | ||
else: | ||
return bytes(plistData) | ||
|
||
|
||
if __name__ == '__main__': | ||
print('This is a library of support tools for the Munki Suite.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# this is needed to make Python recognize the directory as a module package. | ||
# | ||
# Warning: do NOT put any Python imports here that require ObjC. |