def test_cfssl_writes_bundles():
    """Test that CFSSLRefreshCert writes out the bundle if appropriate."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "output": {
            "bundle": "bundle.pem",
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    mocked_open = mock.mock_open()
    mocked_os = mock.MagicMock()

    with mock.patch("cfssl_refresh_cert.open", mocked_open, create=True), \
            mock.patch("cfssl_refresh_cert.os", mocked_os):
        refresher._write_out_cert_files({
            "bundle": "bundle",
            "certificate": "cert",
            "private_key": "key"
        })

    mocked_open.assert_has_calls([
        mock.call("bundle.pem", "w"),
        mock.call().write("bundle"),
        mock.call("server.pem", "w"),
        mock.call().write("cert"),
        mock.call().__exit__(None, None, None),
        mock.call("server-key.pem", "w"),
        mock.call().write("key"),
        mock.call().__exit__(None, None, None)
    ],
                                 any_order=True)

    mocked_os.assert_has_calls([mock.call.chmod("server-key.pem", 0600)])
def test_cfssl_bad_post():
    """Test unsuccessful certificate refresh."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "request": {
                "CN": "testpost",
            }
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               exc=requests.exceptions.ConnectTimeout)

        refresher._write_out_cert_files = mock.MagicMock()

        result = refresher.refresh_cert_and_key()
        assert not result

        # assert POST body is correct
        assert len(m.request_history) == 1
        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "http://127.0.0.1:8888/api/v1/cfssl/newcert"
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}})

        # assert data is never written
        assert not refresher._write_out_cert_files.called
def test_cfssl_ok_with_auth():
    """Test using HTTP auth when refreshing certificate."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "auth": {
                "user": "******",
                "password": "******"
            },
            "request": {
                "CN": "testpost",
            }
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        json_response = {
            "result": {
                "certificate": "cfssl cert",
                "private_key": "cfssl key",
            }
        }
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               json=json_response)

        refresher._write_out_cert_files = mock.MagicMock()

        result = refresher.refresh_cert_and_key()
        assert result

        # assert POST body is correct
        assert len(m.request_history) == 1
        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "http://127.0.0.1:8888/api/v1/cfssl/newcert"
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}})
        assert "authorization" in m.request_history[0]._request.headers
        basic_auth = "Basic {}".format(
            base64.b64encode("{}:{}".format("cfssluser", "cfsslpasswd")))
        assert m.request_history[0]._request.headers["authorization"] == \
            basic_auth

        # assert data is correctly written
        refresher._write_out_cert_files.assert_called_with(\
                json_response["result"])
def test_get_machine_info_ok():
    """Test grabbing machine info from socket package and ipify."""
    refresher = CFSSLRefreshCert()

    with mock.patch("cfssl_refresh_cert.socket") as mock_socket:
        mock_socket_obj = mock.Mock()
        mock_socket.socket.return_value = mock_socket_obj

        mock_socket.gethostname.return_value = "host1"
        mock_socket_obj.getsockname.return_value = ["private"]

        with requests_mock.mock() as m:
            m.get("https://api.ipify.org", text="public")

            machine_info = refresher._get_machine_info()

            assert machine_info == ("host1", "private", "public")
def test_ca_bundle():
    """Test successful certificate refresh."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "request": {
                "CN": "testpost",
            },
            "ca_bundle": "/etc/ssl/certs/ca-certificates.crt"
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        json_response = {
            "result": {
                "certificate": "cfssl cert",
                "private_key": "cfssl key",
            }
        }
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               json=json_response)

        refresher._write_out_cert_files = mock.MagicMock()

        result = refresher.refresh_cert_and_key()
        assert result

        # assert POST body is correct
        assert len(m.request_history) == 1
        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "http://127.0.0.1:8888/api/v1/cfssl/newcert"
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}})

        # assert data is correctly written
        refresher._write_out_cert_files.assert_called_with(\
                json_response["result"])
        req = m.request_history[0]
        assert req.verify == "/etc/ssl/certs/ca-certificates.crt"
def test_cfssl_config_read():
    """Test reading json configuration from a file."""
    refresher = CFSSLRefreshCert()
    d = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "request": {
                "CN": "testpost",
            }
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    m = mock.mock_open(read_data=json.dumps(d))
    with mock.patch("cfssl_refresh_cert.open", m, create=True):
        refresher.read_config_from_file("fake.json")

    assert refresher.config == d
def test_post_to_slack():
    """Validate the JSON POST to Slack."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "onfailure": {
            "post_to_slack": {
                "token": "abcd",
                "channel": "alerts",
            },
        },
    }

    with requests_mock.mock() as m:
        m.post("https://hooks.slack.com/services/abcd")

        refresher._get_machine_info = \
            mock.Mock(return_value=["host", "private", "public"])

        refresher._post_to_slack("testpost", ["line1", "line2"])

        # assert POST body is correct
        assert len(m.request_history) == 1

        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "https://hooks.slack.com/services/abcd"

        request_json_body = json.loads(m.request_history[0].text)

        assert request_json_body["channel"] == "alerts"
        assert request_json_body["text"] == "\n".join([
            "*testpost*", "hostname: host", "private ip: private",
            "public ip: public", "line1", "line2"
        ])
def test_cfssl_ok_with_post_body():
    """Test successful certificate refresh (non-standard profile)."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "post_body": {
                "request": {
                    "CN": "testpost",
                },
                "profile": "non-standard",
            }
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        json_response = {
            "result": {
                "certificate": "cfssl cert",
                "private_key": "cfssl key",
            }
        }
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               json=json_response)

        refresher._write_out_cert_files = mock.MagicMock()

        result = refresher.refresh_cert_and_key()
        assert result

        # assert POST body is correct
        assert len(m.request_history) == 1
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}, "profile":
                    "non-standard"})
def test_post_to_slack_on_execute_command_fail():
    """Test that a Slack message is sent when configured and command fails."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "request": {
                "CN": "testpost",
            }
        },
        "onfailure": {
            "post_to_slack": {
                "token": "abcd",
                "channel": "alerts",
            },
        },
        "onsuccess": {
            "execute_command": "dmesg",
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        json_response = {
            "result": {
                "certificate": "cfssl cert",
                "private_key": "cfssl key",
            }
        }
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               json=json_response)

        refresher._write_out_cert_files = mock.MagicMock()
        refresher._post_to_slack = mock.MagicMock()

        with mock.patch("subprocess.Popen") as mock_subprocess:
            mock_child = mock.Mock()
            mock_subprocess.return_value = mock_child
            mock_child.communicate = mock.Mock(return_value=["out", "err"])
            mock_child.returncode = 1

            result = refresher.refresh_cert_and_key()
            assert not result

        # assert POST body is correct
        assert len(m.request_history) == 1

        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "http://127.0.0.1:8888/api/v1/cfssl/newcert"
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}})

        refresher._post_to_slack.assert_called_with(
            "post cfssl refresh execute command failed!", mock.ANY)
def test_post_to_slack_on_refresh_fail():
    """Test that a Slack message is sent when configured and refresh fails."""
    refresher = CFSSLRefreshCert()
    refresher.config = {
        "cfssl": {
            "url": "http://127.0.0.1:8888",
            "request": {
                "CN": "testpost",
            }
        },
        "onfailure": {
            "post_to_slack": {
                "token": "abcd",
                "channel": "alerts",
            },
        },
        "output": {
            "cert": "server.pem",
            "key": "server-key.pem"
        }
    }

    with requests_mock.mock() as m:
        m.post("http://127.0.0.1:8888/api/v1/cfssl/newcert",
               exc=requests.exceptions.ConnectTimeout)

        refresher._write_out_cert_files = mock.MagicMock()
        refresher._post_to_slack = mock.MagicMock()

        result = refresher.refresh_cert_and_key()
        assert not result

        # assert POST body is correct
        assert len(m.request_history) == 1

        assert m.request_history[0].method == 'POST'
        assert m.request_history[0].url == \
            "http://127.0.0.1:8888/api/v1/cfssl/newcert"
        assert m.request_history[0].text == \
            json.dumps({"request": {"CN": "testpost"}})

        refresher._post_to_slack.assert_called_with("cfssl refresh failed!",
                                                    mock.ANY)