--- /dev/null
+#
+# Copyright 2017 whitestar
+#
+# 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
+#
+# http://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.
+#
+
+# for ver. 3.x
+#source 'https://gpm00.grid.example.com:6280'
+source 'https://supermarket.chef.io'
+
+metadata
concourse-ci CHANGELOG
======================
+0.1.1
+-----
+- adds SSL configurations.
+- improves passwords management.
+- adds the `['concourse-ci']['docker-compose']['(db|web)_password_reset']` attributes.
+- adds the `['concourse-ci']['docker-compose']['ssh_keys_reset']` attribute.
+
0.1.0
-----
- Initial release of concourse-ci
- [concourse-ci::default](#concourse-cidefault)
- [concourse-ci::docker-compose](#concourse-cidocker-compose)
- [Role Examples](#role-examples)
+ - [SSL server keys and certificates management by ssl_cert cookbook](#ssl-server-keys-and-certificates-management-by-ssl_cert-cookbook)
- [License and Authors](#license-and-authors)
## Requirements
|`['concourse-ci']['docker-compose']['pgdata_dir']`|String|Path string or nil (unset).|`"#{node['concourse-ci']['docker-compose']['app_dir']}/database"`|
|`['concourse-ci']['docker-compose']['web_keys_dir']`|String|Path string.|`"#{node['concourse-ci']['docker-compose']['app_dir']}/keys/web"`|
|`['concourse-ci']['docker-compose']['worker_keys_dir']`|String|Path string.|`"#{node['concourse-ci']['docker-compose']['app_dir']}/keys/worker"`|
-|`['concourse-ci']['docker-compose']['pgdata_dir']`|String|Path string or nil (unset).|`"#{node['concourse-ci']['docker-compose']['app_dir']}/database"`|
+|`['concourse-ci']['docker-compose']['pgdata_dir']`|String|Path string or nil (unset, non-persistent).|`"#{node['concourse-ci']['docker-compose']['app_dir']}/database"`|
+|`['concourse-ci']['docker-compose']['db_password_reset']`|String|Only available if the password is automatically generated by Chef.|`false`|
+|`['concourse-ci']['docker-compose']['db_password_vault_item']`|Hash|See `attributes/default.rb`|`{}`|
+|`['concourse-ci']['docker-compose']['web_password_reset']`|String|Only available if the password is automatically generated by Chef.|`false`|
+|`['concourse-ci']['docker-compose']['web_password_vault_item']`|Hash|See `attributes/default.rb`|`{}`|
+|`['concourse-ci']['docker-compose']['ssh_keys_reset']`|String|Resets all SSH keys forcely.|`false`|
|`['concourse-ci']['docker-compose']['config_format_version']`|String|Read only. `docker-compose.yml` format version. Only version 1 is supported now.|`'1'`|
|`['concourse-ci']['docker-compose']['config']`|Hash|`docker-compose.yml` configurations.|See `attributes/default.rb`|
)
```
+- `roles/concourse-with-ssl.rb`
+
+```ruby
+name 'concourse-with-ssl'
+description 'Concourse with SSL'
+
+run_list(
+ 'recipe[ssl_cert::server_key_pairs]',
+ 'role[docker]',
+ 'recipe[concourse-ci::docker-compose]',
+)
+
+image = 'concourse/concourse:2.6.0'
+port = '18443'
+cn = 'concourse.io.example.com'
+
+override_attributes(
+ 'ssl_cert' => {
+ 'common_names' => [
+ cn,
+ ],
+ },
+ 'concourse-ci' => {
+ 'with_ssl_cert_cookbook' => true,
+ 'ssl_cert' => {
+ 'common_name' => cn,
+ },
+ 'docker-compose' => {
+ 'config' => {
+ # Version 1 docker-compose format
+ 'concourse-web' => {
+ 'image' => image,
+ 'ports' => [
+ "#{port}:8443",
+ ],
+ 'environment' => {
+ 'CONCOURSE_EXTERNAL_URL' => "https://192.168.1.3:#{port}",
+ 'CONCOURSE_TLS_BIND_PORT' => '8443', # activate HTTPS
+ # These environments will be set by the concourse-ci::docker-compose recipe automatically.
+ #'CONCOURSE_TLS_CERT' => '/root/server.crt',
+ #'CONCOURSE_TLS_KEY' => '/root/server.key',
+ },
+ 'volumes' => [
+ # These volumes will be set by the concourse-ci::docker-compose recipe automatically.
+ #"#{server_cert_path(node['concourse-ci']['ssl_cert']['common_name'])}:/root/server.crt:ro",
+ #"#{server_key_path(node['concourse-ci']['ssl_cert']['common_name'])}:/root/server.key:ro",
+ ],
+ },
+ 'concourse-worker' => {
+ 'image' => image,
+ },
+ },
+ },
+ },
+)
+```
+
+### SSL server keys and certificates management by ssl_cert cookbook
+
+- create vault items.
+
+```text
+$ ruby -rjson -e 'puts JSON.generate({"private" => File.read("concourse_io_example_com.prod.key")})' \
+> > ~/tmp/concourse_io_example_com.prod.key.json
+
+$ knife vault create ssl_server_keys concourse.io.example.com.prod \
+> --json ~/tmp/concourse_io_example_com.prod.key.json
+
+$ ruby -rjson -e 'puts JSON.generate({"public" => File.read("concourse_io_example_com.prod.crt")})' \
+> > ~/tmp/concourse_io_example_com.prod.crt.json
+
+$ knife vault create ssl_server_certs concourse.io.example.com.prod \
+> --json ~/tmp/concourse_io_example_com.prod.crt.json
+```
+
+- grant reference permission to the Concourse host
+
+```text
+$ knife vault update ssl_server_keys concourse.io.example.com.prod -S 'name:concourse-host.example.com'
+$ knife vault update ssl_server_certs concourse.io.example.com.prod -S 'name:concourse-host.example.com'
+```
+
+- modify run_list and attributes
+
+```ruby
+run_list(
+ 'recipe[ssl_cert::server_key_pairs]',
+ 'recipe[concourse-ci::docker-compose]',
+)
+
+override_attributes(
+ 'ssl_cert' => {
+ 'common_names' => [
+ 'concourse.io.example.com',
+ ],
+ },
+ 'concourse-ci' => {
+ 'with_ssl_cert_cookbook' => true,
+ 'ssl_cert' => {
+ 'common_name' => 'concourse.io.example.com',
+ },
+ # ...
+ },
+)
+```
+
## License and Authors
- Author:: whitestar at osdn.jp
# limitations under the License.
#
-# TODO: support with_ssl_cert_cookbook
-force_override['concourse-ci']['with_ssl_cert_cookbook'] = false
+default['concourse-ci']['with_ssl_cert_cookbook'] = false
# If ['concourse-ci']['with_ssl_cert_cookbook'] is true,
# node['concourse-ci']['docker-compose']['config']
# are overridden by the following 'common_name' attributes.
default['concourse-ci']['docker-compose']['pgdata_dir'] = "#{node['concourse-ci']['docker-compose']['app_dir']}/database"
default['concourse-ci']['docker-compose']['web_keys_dir'] = "#{node['concourse-ci']['docker-compose']['app_dir']}/keys/web"
default['concourse-ci']['docker-compose']['worker_keys_dir'] = "#{node['concourse-ci']['docker-compose']['app_dir']}/keys/worker"
+default['concourse-ci']['docker-compose']['db_password_reset'] = false
+default['concourse-ci']['docker-compose']['db_password_vault_item'] = {
+=begin
+ 'vault' => 'concourse',
+ 'name' => 'db_password',
+ # single password or nested hash password path delimited by slash
+ 'env_context' => false,
+ 'key' => 'password', # real hash path: "/password"
+ # or nested hash password path delimited by slash
+ #'env_context' => true,
+ #'key' => 'hash/path/to/password', # real hash path: "/#{node.chef_environment}/hash/path/to/password"
+=end
+}
+default['concourse-ci']['docker-compose']['web_password_reset'] = false
+default['concourse-ci']['docker-compose']['web_password_vault_item'] = {
+=begin
+ 'vault' => 'concourse',
+ 'name' => 'web_password',
+ # single password or nested hash password path delimited by slash
+ 'env_context' => false,
+ 'key' => 'password', # real hash path: "/password"
+ # or nested hash password path delimited by slash
+ #'env_context' => true,
+ #'key' => 'hash/path/to/password', # real hash path: "/#{node.chef_environment}/hash/path/to/password"
+=end
+}
+default['concourse-ci']['docker-compose']['ssh_keys_reset'] = false
# TODO: support version 2 format, and use `default` instead of `force_override`
force_override['concourse-ci']['docker-compose']['config_format_version'] = '1'
'environment' => {
'POSTGRES_DB' => 'concourse',
'POSTGRES_USER' => 'concourse',
+ # Note: You should use the `['concourse-ci']['docker-compose']['db_password_vault_item']` attribute.
'POSTGRES_PASSWORD' => nil,
'PGDATA' => '/database',
},
'command' => 'web',
'ports' => [
#'8080:8080', # If you sepecify no value, Chef will sets '8080:8080'.
+ #'8443:8443', # https
],
'volumes' => [
"#{node['concourse-ci']['docker-compose']['web_keys_dir']}:/concourse-keys",
],
'environment' => {
+ #'CONCOURSE_TLS_BIND_PORT' => '8443', # activate HTTPS
'CONCOURSE_BASIC_AUTH_USERNAME' => 'concourse',
+ # Note: You should use the `['concourse-ci']['docker-compose']['web_password_vault_item']` attribute.
'CONCOURSE_BASIC_AUTH_PASSWORD' => nil,
# If you sepecify no value, Chef will sets "http://#{node['ipaddress']}:8080".
'CONCOURSE_EXTERNAL_URL' => nil,
license 'Apache 2.0'
description 'Installs/Configures Concourse CI by Docker Compose'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
-version '0.1.0'
+version '0.1.1'
source_url 'http://scm.osdn.jp/gitroot/metasearch/grid-chef-repo.git'
issues_url 'https://osdn.jp/projects/metasearch/ticket'
depends 'docker-grid', '>= 0.2.7'
-depends 'ssl_cert', '>= 0.3.3'
+depends 'ssl_cert', '>= 0.3.7'
# limitations under the License.
#
+::Chef::Recipe.send(:include, SSLCert::Helper)
+
require 'securerandom'
doc_url = 'https://concourse.ci/docker-repository.html'
pgdata_dir = node['concourse-ci']['docker-compose']['pgdata_dir']
web_keys_dir = node['concourse-ci']['docker-compose']['web_keys_dir']
worker_keys_dir = node['concourse-ci']['docker-compose']['worker_keys_dir']
-#certs_dir = "#{app_dir}/certs"
[
app_dir,
- pgdata_dir,
web_keys_dir,
worker_keys_dir,
- #certs_dir,
].each {|dir|
resources(directory: dir) rescue directory dir do
owner 'root'
group 'root'
mode '0755'
recursive true
- end if !dir.nil? && !dir.empty?
+ end
}
+# DB persistent
+resources(directory: pgdata_dir) rescue directory pgdata_dir do
+ #owner 999
+ group 'root'
+ mode '0700'
+ recursive true
+end if !pgdata_dir.nil? && !pgdata_dir.empty?
+
bash 'ssh-keygen_keys' do
code <<-"EOH"
- script commands
+ rm -f #{web_keys_dir}/*
ssh-keygen -t rsa -f #{web_keys_dir}/tsa_host_key -N ''
ssh-keygen -t rsa -f #{web_keys_dir}/session_signing_key -N ''
+ rm -f #{worker_keys_dir}/*
ssh-keygen -t rsa -f #{worker_keys_dir}/worker_key -N ''
cp #{worker_keys_dir}/worker_key.pub #{web_keys_dir}/authorized_worker_keys
cp #{web_keys_dir}/tsa_host_key.pub #{worker_keys_dir}
EOH
action :run
- not_if { File.exist?("#{web_keys_dir}/tsa_host_key") }
- not_if { File.exist?("#{web_keys_dir}/session_signing_key") }
- not_if { File.exist?("#{worker_keys_dir}/worker_key") }
+ unless node['concourse-ci']['docker-compose']['ssh_keys_reset']
+ not_if { File.exist?("#{web_keys_dir}/tsa_host_key") }
+ not_if { File.exist?("#{web_keys_dir}/session_signing_key") }
+ not_if { File.exist?("#{worker_keys_dir}/worker_key") }
+ end
+end
+
+config_file = "#{app_dir}/docker-compose.yml"
+config_srvs_local = nil
+if File.exist?(config_file)
+ require 'yaml'
+ config_srvs_local = YAML.load_file(config_file)
+ config_srvs_local = config_srvs_local['services'] if config_srvs_local.key?('version') && config_srvs_local['version'] == '2'
end
config_format_version = node['concourse-ci']['docker-compose']['config_format_version']
# Database
db_envs_org = config_srvs['concourse-db']['environment']
db_envs = {}
-db_vols = []
-
-db_passwd = db_envs_org['POSTGRES_PASSWORD']
-if db_passwd.nil? || db_passwd.empty?
- db_passwd = SecureRandom.hex # or urlsafe_base64
- db_envs['POSTGRES_PASSWORD'] = db_passwd
+db_vols = config_srvs['concourse-db']['volumes'].to_a
+
+db_passwd = nil
+db_password_vault_item = node['concourse-ci']['docker-compose']['db_password_vault_item']
+if !db_password_vault_item.empty?
+ # 1.
+ db_passwd = get_vault_item_value(db_password_vault_item)
+else
+ # 2.
+ db_passwd = db_envs_org['POSTGRES_PASSWORD']
+ if db_passwd.nil? || db_passwd.empty?
+ db_passwd = \
+ if !config_srvs_local.nil? && !node['concourse-ci']['docker-compose']['db_password_reset']
+ # 3.
+ config_srvs_local['concourse-db']['environment']['POSTGRES_PASSWORD']
+ else
+ # 4.
+ SecureRandom.hex # or urlsafe_base64
+ end
+ end
end
+# prevent Chef from logging password attribute value. (=> template variables)
+#db_envs['POSTGRES_PASSWORD'] = db_passwd
db_vols.push("#{pgdata_dir}:#{db_envs_org['PGDATA']}") if !pgdata_dir.nil? && !pgdata_dir.empty?
+# merge environment hash
force_override_config_srvs['concourse-db']['environment'] = db_envs unless db_envs.empty?
+# reset vlumes array.
override_config_srvs['concourse-db']['volumes'] = db_vols unless db_vols.empty?
# Web
web_envs_org = config_srvs['concourse-web']['environment']
web_envs = {}
+web_vols = config_srvs['concourse-web']['volumes'].to_a
web_ports = config_srvs['concourse-web']['ports']
override_config_srvs['concourse-web']['ports'] = ['8080:8080'] if web_ports.empty?
-basic_auth_passwd = web_envs_org['CONCOURSE_BASIC_AUTH_PASSWORD']
-if basic_auth_passwd.nil? || basic_auth_passwd.empty?
- basic_auth_passwd = SecureRandom.hex # or urlsafe_base64
- web_envs['CONCOURSE_BASIC_AUTH_PASSWORD'] = basic_auth_passwd
+basic_auth_passwd = nil
+web_password_vault_item = node['concourse-ci']['docker-compose']['web_password_vault_item']
+if !web_password_vault_item.empty?
+ # 1.
+ basic_auth_passwd = get_vault_item_value(web_password_vault_item)
+else
+ # 2.
+ basic_auth_passwd = web_envs_org['CONCOURSE_BASIC_AUTH_PASSWORD']
+ if basic_auth_passwd.nil? || basic_auth_passwd.empty?
+ basic_auth_passwd = \
+ if !config_srvs_local.nil? && !node['concourse-ci']['docker-compose']['web_password_reset']
+ # 3.
+ config_srvs_local['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD']
+ else
+ # 4.
+ SecureRandom.hex # or urlsafe_base64
+ end
+ end
end
+# prevent Chef from logging password attribute value. (=> template variables)
+#web_envs['CONCOURSE_BASIC_AUTH_PASSWORD'] = basic_auth_passwd
external_url = web_envs_org['CONCOURSE_EXTERNAL_URL']
web_envs['CONCOURSE_EXTERNAL_URL'] = "http://#{node['ipaddress']}:8080" if external_url.nil?
data_source = web_envs_org['CONCOURSE_POSTGRES_DATA_SOURCE']
-data_source.gsub!(/<POSTGRES_PASSWORD>/, db_passwd)
-web_envs['CONCOURSE_POSTGRES_DATA_SOURCE'] = data_source
-
-force_override_config_srvs['concourse-web']['environment'] = web_envs unless web_envs.empty?
+data_source = data_source.gsub(/<POSTGRES_PASSWORD>/, db_passwd)
+# prevent Chef from logging password attribute value. (=> template variables)
+#web_envs['CONCOURSE_POSTGRES_DATA_SOURCE'] = data_source
-=begin
if node['concourse-ci']['with_ssl_cert_cookbook']
::Chef::Recipe.send(:include, SSLCert::Helper)
cn = node['concourse-ci']['ssl_cert']['common_name']
- key_path = server_key_path(cn)
-
- bash 'copy_ssl_server_key' do
- code <<-EOH
- cp #{key_path} #{certs_dir}/server.key
- chmod 600 #{certs_dir}/server.key
- EOH
- sensitive true
- action :nothing
- subscribes :run, "file[#{key_path}]"
- end
-
- # TODO: key path setup.
+ # Concourse web process owner is root.
+ web_vols.push("#{server_cert_path(cn)}:/root/server.crt:ro")
+ web_vols.push("#{server_key_path(cn)}:/root/server.key:ro")
+ web_envs['CONCOURSE_TLS_CERT'] = '/root/server.crt'
+ web_envs['CONCOURSE_TLS_KEY'] = '/root/server.key'
end
-=end
-template "#{app_dir}/docker-compose.yml" do
+# merge environment hash
+force_override_config_srvs['concourse-web']['environment'] = web_envs unless web_envs.empty?
+# reset vlumes array.
+override_config_srvs['concourse-web']['volumes'] = web_vols unless web_vols.empty?
+
+template config_file do
source 'opt/docker-compose/app/concourse/docker-compose.yml'
owner 'root'
group 'root'
mode '0600'
sensitive true
+ # prevent Chef from logging password attribute value.
+ variables(
+ db_passwd: db_passwd,
+ basic_auth_passwd: basic_auth_passwd,
+ data_source: data_source
+ )
end
log <<-"EOM"
<%
-config = node['concourse-ci']['docker-compose']['config']
+config_format_version = node['concourse-ci']['docker-compose']['config_format_version']
+config = node['concourse-ci']['docker-compose']['config'].to_hash
+
+config_srvs = \
+ if config_format_version == '1'
+ config
+ elsif config_format_version == '2'
+ config['services']
+ end
+
+# prevent Chef from logging password attribute value.
+config_srvs['concourse-db']['environment']['POSTGRES_PASSWORD'] = @db_passwd
+config_srvs['concourse-web']['environment']['CONCOURSE_BASIC_AUTH_PASSWORD'] = @basic_auth_passwd
+config_srvs['concourse-web']['environment']['CONCOURSE_POSTGRES_DATA_SOURCE'] = @data_source
require 'yaml'
-yaml_str = config.to_hash.to_yaml
+yaml_str = config.to_yaml
-%>
<%= yaml_str %>
description 'Concourse'
run_list(
- #'recipe[ssl_cert::server_key_pairs]',
+ #'recipe[ssl_cert::server_key_pairs]', # for https
'role[docker]',
'recipe[concourse-ci::docker-compose]',
)
#default_attributes()
image = 'concourse/concourse' # of 'concourse/concourse:2.6.0',...
-port = '8080'
+port = '8080' # '8443' (for https)
concourse_cn = 'concourse.io.example.com'
override_attributes(
+ # for https
+ 'ssl_cert' => {
+ 'common_names' => [
+ concourse_cn,
+ ],
+ },
'concourse-ci' => {
- # Not supported yet.
- 'with_ssl_cert_cookbook' => false,
+ 'with_ssl_cert_cookbook' => false, # or true (for https)
'ssl_cert' => {
'common_name' => concourse_cn,
},
'image' => image,
'ports' => [
#"#{port}:8080", # If you sepecify no value, Chef will sets '8080:8080'.
+ #"#{port}:8443", # for https
],
'environment' => {
# If you sepecify no value, Chef will sets "http://#{node['ipaddress']}:8080".
- #'CONCOURSE_EXTERNAL_URL' => "http://192.168.1.3:#{port}",
+ #'CONCOURSE_EXTERNAL_URL' => "http://192.168.1.3:#{port}", # or "https://192.168.1.3:#{port}"
+ #'CONCOURSE_TLS_BIND_PORT' => '8443', # activate HTTPS
+ # These environments will be set by the concourse-ci::docker-compose recipe automatically.
+ #'CONCOURSE_TLS_CERT' => '/root/server.crt',
+ #'CONCOURSE_TLS_KEY' => '/root/server.key',
},
+ 'volumes' => [
+ # These volumes will be set by the concourse-ci::docker-compose recipe automatically.
+ #"#{server_cert_path(node['concourse-ci']['ssl_cert']['common_name'])}:/root/server.crt:ro",
+ #"#{server_key_path(node['concourse-ci']['ssl_cert']['common_name'])}:/root/server.key:ro",
+ ],
},
'concourse-worker' => {
'image' => image,
},
},
},
- 'ssl_cert' => {
- 'common_names' => [
- concourse_cn,
- ],
- },
)