diff --git a/.gitignore b/.gitignore index febee30..3a2fc87 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ bin/* Berksfile.lock .zero-knife.rb Policyfile.lock.json +.chef/ diff --git a/.kitchen.yml b/.kitchen.yml index 00a6151..0927693 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -19,16 +19,20 @@ platforms: - name: centos-7.3 driver: provider: vmware_workstation + - name: fedora-25 + driver: + provider: vmware_workstation + - name: debian-8.7 + driver: + provider: vmware_workstation suites: - name: default run_list: - - recipe[chef-zerotier::install] - - recipe[chef-zerotier::ohai_plugin] + - recipe[zerotier::install] + - recipe[zerotier::ohai_plugin] + - recipe[zerotier::join_networks] #verifier: # inspec_tests: # - test/smoke/install attributes: - ohai: - plugin_path: /tmp/kitchen/ohai/plugins - diff --git a/README.md b/README.md index 5716916..8965fce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,67 @@ -# chef-zerotier +ZeroTier Cookbook +================== -TODO: Enter the cookbook description here. +This is a [Chef](https://www.chef.io/) cookbook to manage [ZeroTier](https://www.zerotier.com) networks on your Chef nodes. +Supported Platforms +--------------------- +* Debian +* Ubuntu +* CentOS +* RHEL +* Amazon + +Recipes +--------------------- +`zerotier::default` + +Default recipe. Calls `zerotier::install` + +`zerotier::install` + +Install's ZeroTier One on your system via the native package management system. + +`zerotier::ohai_plugin` + +Installs the Ohai plugin for ZeroTier. This is required by the provided LWRP `zerotier_network`. + +`zerotier::join_networks` + +Shortcut to automatically join networks stored in attributes (See example in the Attributes section below) + +Attributes +--------------------- +`node['zerotier']['version']` + +Version of ZeroTier to install. Empty by default and defaults to the latest version available. + +`node['zerotier']['central_url']` + +URL to the instance of the ZeroTier Central controller. Defaults to https://my.zerotier.com. Will be useful in the future when Central is distributable to our enterprise customers. + +`node['zerotier']['public_autojoin']` + +List of *public* networks to automatically join when using the `zerotier::join_networks` recipe. These networks do not require any interaction with the network controller. + +`node['zerotier']['private_autojoin']` + +List of *private* networks to automatically join when using the `zerotier::join_networks` recipe. Joining a private network requires an API Access Token generated at https://my.zerotier.com. Each member of the list is a hash as follows: + +``` +{ + :network_id => "your_network_id", + :auth_token => "your_auth_token", # API access token generated at https://my.zerotier.com + :central_url => "URL_to_central_instance" # Not required. Defaults to https://my.zerotier.com +} +``` + +LWRP +--------------------- +The ZeroTier recpie provides the `zerotier_network` lwrp + +Attributes: + +- network_id - Network ID to join. defaults to the name attribute. +- node_name - Name of the node to put in https://my.zerotier.com (only applicable when joining a private network) +- auth_token - API access token generated in your account at https://my.zerotier.com. Required if you wish to automatically authorize the node to join the network. +- central_url - URL to the instance of ZeroTier Central. Defaults to https://my.zerotier.com. diff --git a/attributes/default.rb b/attributes/default.rb index 6150abb..f18caac 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -1,16 +1,23 @@ +# Not set by default. +# default['zerotier']['version'] +# +default['zerotier']['central_url'] = 'https://my.zerotier.com' -default['zt']['central_url'] = 'https://my.zerotier.com' -default['zt']['api_url'] = URI.join(node['zt']['central_url'], '/api/') - -# Public networks to autojoin. Does not require api_key -default['zt']['public_autojoin'] = [] +# Public networks to autojoin. +default['zerotier']['public_autojoin'] = [] # Private networks to autojoin. Requires ZeroTier Central API api key. # # Packed in the following format: # -# [{:network_id => "", :api_key => "key"},...] +# [{ +# :network_id => "", +# :auth_token => "key", +# :central_url => "http://my.zerotier.com" // optional. Defaults to https://my.zerotier.com +# }, +# ... +# ] # -default['zt']['private_autojoin'] = [] +default['zerotier']['private_autojoin'] = [] diff --git a/files/default/zerotier_ohai.rb b/files/default/zerotier_ohai.rb index 8c22099..06f489b 100644 --- a/files/default/zerotier_ohai.rb +++ b/files/default/zerotier_ohai.rb @@ -2,10 +2,35 @@ Ohai.plugin(:ZeroTier) do provides 'zerotier' - def linux_get_interfaces - interfaces = Mash.new + def linux_get_networks + networks = Mash.new - interfaces + so = shell_out('/usr/sbin/zerotier-cli listnetworks') + first_line = true + so.stdout.lines do |line| + if first_line + # skip the header line + first_line = false + next + end + + data = line.strip.split(/\s+/) + + cur_network = Mash.new + cur_network[:network_name] = data[3] + cur_network[:mac] = data[4] + cur_network[:status] = data[5] + cur_network[:type] = data[6] + cur_network[:interface] = data[7] + cur_network[:addresses] = [] + + data[8].split(',').each do |addr| + cur_network[:addresses].push(addr) + end + + networks[data[2]] = cur_network + end + return networks end def linux_get_node_id @@ -37,8 +62,9 @@ def find_zerotier collect_data(:linux) do if find_zerotier zerotier Mash.new - zerotier[:version] = get_version - zerotier[:node_id] = linux_get_node_id + zerotier[:version] = get_version + zerotier[:node_id] = linux_get_node_id + zerotier[:networks] = linux_get_networks else Ohai::Log.warn("Cannot find zerotier-cli") end diff --git a/metadata.rb b/metadata.rb index 3ae8537..0d418fc 100644 --- a/metadata.rb +++ b/metadata.rb @@ -1,22 +1,12 @@ -name 'chef-zerotier' +name 'zerotier' maintainer 'Grant Limberg' maintainer_email 'grant.limberg@zerotier.com' license 'GPL v3' description 'Installs/Configures ZeroTier' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version '0.1.0' - -# The `issues_url` points to the location where issues for this cookbook are -# tracked. A `View Issues` link will be displayed on this cookbook's page when -# uploaded to a Supermarket. -# -# issues_url 'https://github.com//chef-zerotier/issues' if respond_to?(:issues_url) - -# The `source_url` points to the development reposiory for this cookbook. A -# `View Source` link will be displayed on this cookbook's page when uploaded to -# a Supermarket. -# -# source_url 'https://github.com//chef-zerotier' if respond_to?(:source_url) +version '1.0.0' +issues_url 'https://github.com/zerotier/chef-zerotier/issues' if respond_to?(:issues_url) +source_url 'https://github.com/zerotier/chef-zerotier' if respond_to?(:source_url) %w(redhat centos amazon ubuntu debian).each do |os| supports os diff --git a/providers/network.rb b/providers/network.rb new file mode 100644 index 0000000..f3d8d75 --- /dev/null +++ b/providers/network.rb @@ -0,0 +1,78 @@ +require 'chef/log' +require "net/http" +require "net/https" +require "uri" +require "json" +require 'mixlib/shellout' + +use_inline_resources + +def load_current_resource + @current_resource = Chef::Resource::ZerotierNetwork.new(new_resource.network_id) + @current_resource.node_name(new_resource.node_name) + @current_resource.auth_token(new_resource.auth_token) + @current_resource.central_url(new_resource.central_url) + @current_resource +end + +def whyrun_supported? + true +end + +action :join do + if ::File.exists?("/var/lib/zerotier-one/networks.d/#{new_resource.network_id}.conf") + Chef::Log.info("Network #{new_resource.network_id} already joined. Skipping.") + else + converge_by("Joining Network #{new_resource.network_id}") do + join = Mixlib::ShellOut.new("/usr/sbin/zerotier-cli join #{new_resource.network_id}") + join.run_command + raise "Error joining network #{new_resource.network_id}" if join.error? + + if new_resource.auth_token + url = URI.parse("#{new_resource.central_url}/api/network/#{new_resource.network_id}/member/#{node['zerotier']['node_id']}/") + + netinfo = { + :networkId => new_resource.network_id, + :nodeId => node['zerotier']['node_id'], + :name => new_resource.node_name, + :config => { + :nwid => new_resource.network_id, + :authorized => true + } + } + + response = Net::HTTP.start(url.host, url.port, :use_ssl => url.scheme == 'https') do |http| + post = Net::HTTP::Post.new(url, 'Content-Type' => 'application/json') + post.add_field('Authorization', "Bearer #{new_resource.auth_token}") + post.body = netinfo.to_json + http.request(post) + end + + case response + when Net::HTTPSuccess + # do nothing + else + leave = Mixlib::ShellOut.new("/usr/sbin/zerotier-cli leave #{new_resource.network_id}") + leave.run_command + error = JSON.parse(response.body) + raise "Error #{response.code} authorizing network: #{error['type']}: #{error['message']}" + end + end + + new_resource.updated_by_last_action(true) + end + end +end + +action :leave do + if ::File.exists?("/var/lib/zerotier-one/networks.d/#{new_resource.network_id}.conf") + converge_by("Leaving network #{new_resource.network_id}") do + leave = Mixlib::ShellOut.new("/usr/sbin/zerotier-cli leave #{new_resource.network_id}") + leave.run_command + raise "Error leaving network #{new_resource.network_id}" if leave.error? + new_resource.updated_by_last_action(true) + end + else + Chef::Log.warn("Network #{new_resource.network_id} is not joined. Skipping.") + end +end diff --git a/recipes/default.rb b/recipes/default.rb index d90caf3..55e436d 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -1,9 +1,7 @@ # -# Cookbook:: chef-zerotier +# Cookbook:: zerotier # Recipe:: default # # Copyright:: 2017, ZeroTier, Inc., All Rights Reserved. -include_recipe 'chef-zerotier::install' - -include_recipe 'chef-zerotier::ohai_plugin' +include_recipe 'zerotier::install' diff --git a/recipes/install.rb b/recipes/install.rb index 5881aad..789800d 100644 --- a/recipes/install.rb +++ b/recipes/install.rb @@ -1,12 +1,9 @@ # -# Cookbook:: . +# Cookbook:: zerotier # Recipe:: install # # Copyright:: 2017, ZeroTier, Inc., All Rights Reserved. - - - case node['platform'] when 'debian', 'ubuntu' apt_repository 'zerotier' do @@ -19,27 +16,27 @@ yum_repository 'zerotier' do description "ZeroTier Repo" baseurl 'https://download.zerotier.com/redhat/el/$releasever' - gpgcheck false + gpgkey 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg' end when 'amazon' yum_repository 'zerotier' do description 'ZeroTier Repo' baseurl 'https://download.zerotier.com/redhat/amzn1/' - gpgcheck false + gpgkey 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg' end when 'fedora' yum_repository 'zerotier' do description 'ZeroTier Repo' - baseurl 'https://download.zerotier.com/redhat/fc/' - gpgcheck false + baseurl 'https://download.zerotier.com/redhat/fc/22' + gpgkey 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg' end else Chef::Log.fatal("Platform '#{node['platform']}' is not yet supported by this recipe") end package 'zerotier-one' do - if node['zt']['version'] - version node['zt']['version'] + if node['zerotier']['version'] + version node['zerotier']['version'] end end @@ -47,3 +44,5 @@ action [:enable, :start] supports :status => true, :restart => true, :start => true,:stop => true end + +include_recipe 'zerotier::ohai_plugin' \ No newline at end of file diff --git a/recipes/join_networks.rb b/recipes/join_networks.rb new file mode 100644 index 0000000..061bd7d --- /dev/null +++ b/recipes/join_networks.rb @@ -0,0 +1,23 @@ +# +# Cookbook:: zerotier +# Recipe:: join_networks +# +# Copyright:: 2017, ZeroTier, Inc., All Rights Reserved. + +include_recipe 'zerotier::ohai_plugin' + +node['zerotier']['public_autojoin'].each do |nwid| + zerotier_network nwid do + action :join + end +end + +node['zerotier']['private_autojoin'].each do |network| + zerotier_network network['network_id'] do + only_if { network.key?("auth_token") } + action :join + auth_token network['auth_token'] + central_url network.key?("central_url") ? network[:central_url] : "https://my.zerotier.com" + node_name node['fqdn'] + end +end \ No newline at end of file diff --git a/recipes/ohai_plugin.rb b/recipes/ohai_plugin.rb index 7b89945..b27facc 100644 --- a/recipes/ohai_plugin.rb +++ b/recipes/ohai_plugin.rb @@ -1,9 +1,9 @@ # -# Cookbook:: chef-zerotier +# Cookbook:: zerotier # Recipe:: ohai_plugin # # Copyright:: 2017, ZeroTier, Inc., All Rights Reserved. -include_recipe 'chef-zerotier::install' - -ohai_plugin 'zerotier_ohai' +ohai_plugin 'zerotier_ohai' do + compile_time false +end diff --git a/resources/network.rb b/resources/network.rb new file mode 100644 index 0000000..a220c1b --- /dev/null +++ b/resources/network.rb @@ -0,0 +1,7 @@ +actions :join, :leave +default_action :join + +attribute :network_id, :kind_of => String, :name_attribute => true, :required => true +attribute :node_name, :kind_of => String, :required => false +attribute :auth_token, :kind_of => String, :required => false +attribute :central_url, :kind_of => String, :default => "https://my.zerotier.com" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 1dd5126..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'chefspec' -require 'chefspec/berkshelf' diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb deleted file mode 100644 index 2a065bc..0000000 --- a/spec/unit/recipes/default_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# -# Cookbook:: chef-zerotier -# Spec:: default -# -# Copyright:: 2017, The Authors, All Rights Reserved. - -require 'spec_helper' - -describe 'chef-zerotier::default' do - context 'When all attributes are default, on an unspecified platform' do - let(:chef_run) do - runner = ChefSpec::ServerRunner.new - runner.converge(described_recipe) - end - - it 'converges successfully' do - expect { chef_run }.to_not raise_error - end - end -end diff --git a/spec/unit/recipes/install_spec.rb b/spec/unit/recipes/install_spec.rb deleted file mode 100644 index c09a4f7..0000000 --- a/spec/unit/recipes/install_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# -# Cookbook:: . -# Spec:: default -# -# Copyright:: 2017, The Authors, All Rights Reserved. - -require 'spec_helper' - -describe '.::install' do - context 'When all attributes are default, on an unspecified platform' do - let(:chef_run) do - runner = ChefSpec::ServerRunner.new - runner.converge(described_recipe) - end - - it 'converges successfully' do - expect { chef_run }.to_not raise_error - end - end -end diff --git a/spec/unit/recipes/ohai_plugin_spec.rb b/spec/unit/recipes/ohai_plugin_spec.rb deleted file mode 100644 index c1ee764..0000000 --- a/spec/unit/recipes/ohai_plugin_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# -# Cookbook:: . -# Spec:: default -# -# Copyright:: 2017, The Authors, All Rights Reserved. - -require 'spec_helper' - -describe '.::ohai_plugin' do - context 'When all attributes are default, on an unspecified platform' do - let(:chef_run) do - runner = ChefSpec::ServerRunner.new - runner.converge(described_recipe) - end - - it 'converges successfully' do - expect { chef_run }.to_not raise_error - end - end -end diff --git a/test/smoke/default/default_test.rb b/test/smoke/default/default_test.rb deleted file mode 100644 index 83551a6..0000000 --- a/test/smoke/default/default_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -# # encoding: utf-8 - -# Inspec test for recipe chef-zerotier::default - -# The Inspec reference, with examples and extensive documentation, can be -# found at http://inspec.io/docs/reference/resources/ - -unless os.windows? - describe user('root') do - it { should exist } - skip 'This is an example test, replace with your own test.' - end -end - -describe port(80) do - it { should_not be_listening } - skip 'This is an example test, replace with your own test.' -end diff --git a/test/smoke/default/install.rb b/test/smoke/default/install.rb deleted file mode 100644 index 31a9aa4..0000000 --- a/test/smoke/default/install.rb +++ /dev/null @@ -1,13 +0,0 @@ -# # encoding: utf-8 - -# Inspec test for recipe .::install - -# The Inspec reference, with examples and extensive documentation, can be -# found at http://inspec.io/docs/reference/resources/ - -def node - JSON.parse(IO.read('/tmp/kitchen/chef_node.json')) -end - - - diff --git a/test/smoke/default/ohai_plugin.rb b/test/smoke/default/ohai_plugin.rb deleted file mode 100644 index 17dce25..0000000 --- a/test/smoke/default/ohai_plugin.rb +++ /dev/null @@ -1,18 +0,0 @@ -# # encoding: utf-8 - -# Inspec test for recipe .::ohai_plugin - -# The Inspec reference, with examples and extensive documentation, can be -# found at http://inspec.io/docs/reference/resources/ - -unless os.windows? - describe user('root') do - it { should exist } - skip 'This is an example test, replace with your own test.' - end -end - -describe port(80) do - it { should_not be_listening } - skip 'This is an example test, replace with your own test.' -end