mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[#1304] Checkpoint: updated shell + http basic_auth
This commit is contained in:
@@ -15,7 +15,7 @@ kea-shell - Text client for Control Agent process
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
:program:`kea-shell` [**-h**] [**-v**] [**--host**] [**--port**] [**--path**] [**--timeout**] [**--service**] [command]
|
||||
:program:`kea-shell` [**-h**] [**-v**] [**--host**] [**--port**] [**--path**] [**--auth-user**] [**--auth-password**] [**--timeout**] [**--service**] [command]
|
||||
|
||||
Description
|
||||
~~~~~~~~~~~
|
||||
@@ -50,6 +50,14 @@ The arguments are as follows:
|
||||
path is used. As Control Agent listens at the empty path, this
|
||||
parameter is useful only with a reverse proxy.
|
||||
|
||||
``--auth-user``
|
||||
Specifies the user name for basic HTTP authentication. If not specified
|
||||
or specified as the empty string authentication is not used.
|
||||
|
||||
``--auth-password``
|
||||
Specifies the password for basic HTTP authentication. If not specified
|
||||
but the user name is specified an empty password is used.
|
||||
|
||||
``--timeout``
|
||||
Specifies the connection timeout in seconds. If not specified, 10
|
||||
(seconds) is used.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!@PYTHON@
|
||||
|
||||
# Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
# Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -21,6 +21,7 @@ import os
|
||||
import sys
|
||||
import signal
|
||||
import argparse
|
||||
from base64 import b64encode
|
||||
|
||||
sys.path.append('@PKGPYTHONDIR@')
|
||||
|
||||
@@ -69,6 +70,10 @@ def shell_body():
|
||||
parser.add_argument('--service', nargs="?", action="append",
|
||||
help='target spcified service. If not specified,'
|
||||
'control agent will receive command.')
|
||||
parser.add_argument('--auth-user', type=str, default='',
|
||||
help='Basic HTTP authentication user')
|
||||
parser.add_argument('--auth-password', type=str, default='',
|
||||
help='Basic HTTP authentication password')
|
||||
parser.add_argument('command', type=str, nargs="?",
|
||||
default='list-commands',
|
||||
help='command to be executed. If not specified, '
|
||||
@@ -88,6 +93,14 @@ def shell_body():
|
||||
params.http_host = cmd_args.host
|
||||
params.http_port = cmd_args.port
|
||||
params.path += cmd_args.path
|
||||
if cmd_args.auth_user is not '':
|
||||
user = cmd_args.auth_user.encode('latin1')
|
||||
password = cmd_args.auth_password.encode('latin1')
|
||||
secret = b':'.join((user, password))
|
||||
if sys.version_info[0] == 3:
|
||||
params.auth = b64encode(secret).strip().decode('ascii')
|
||||
else:
|
||||
params.auth = b64encode(secret).strip().encode('ascii')
|
||||
params.timeout = cmd_args.timeout
|
||||
params.version = VERSION
|
||||
|
||||
|
@@ -11,6 +11,8 @@ kea-shell
|
||||
--host
|
||||
--port
|
||||
--path
|
||||
--auth-user
|
||||
--auth-password
|
||||
--timeout
|
||||
--service
|
||||
command
|
||||
@@ -47,6 +49,14 @@ The arguments are as follows:
|
||||
path is used. As Control Agent listens at the empty path this
|
||||
parameter is useful only with a reverse proxy.
|
||||
|
||||
``--auth-user``
|
||||
Specifies the user name for basic HTTP authentication. If not specified
|
||||
or specified as the empty string authentication is not used.
|
||||
|
||||
``--auth-password``
|
||||
Specifies the password for basic HTTP authentication. If not specified
|
||||
but the user name is specified an empty password is used.
|
||||
|
||||
``--timeout``
|
||||
Specifies the connection timeout in seconds. If not specified, 10
|
||||
(seconds) is used.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
# Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -18,6 +18,7 @@ class CARequest:
|
||||
- command - specifies the command to send (e.g. list-commands)
|
||||
- service - specifies service that is target for the command (e.g. dhcp4)
|
||||
- timeout - timeout (in ms)
|
||||
- auth - basic HTTP authentication credential
|
||||
- args - extra arguments my be added here
|
||||
- headers - extra HTTP headers may be added here
|
||||
- version - version to be reported in HTTP header
|
||||
@@ -28,6 +29,7 @@ class CARequest:
|
||||
command = ''
|
||||
service = ''
|
||||
timeout = 0
|
||||
auth = None
|
||||
args = ''
|
||||
headers = {}
|
||||
version = ""
|
||||
@@ -55,9 +57,11 @@ class CARequest:
|
||||
|
||||
In particular, this method generates Content-Length and its value.
|
||||
"""
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
self.headers['User-Agent'] = "Kea-shell/%s"%(self.version)
|
||||
self.headers['Accept'] = '*/*'
|
||||
if self.auth is not None:
|
||||
self.headers['Authorization'] = "Basic %s"%(self.auth)
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
self.headers['Content-Length'] = "%d"%(len(self.content))
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!@PYTHON@
|
||||
|
||||
# Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
# Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -11,6 +11,8 @@ Kea shell unittest (python part)
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
from base64 import b64encode
|
||||
|
||||
from kea_conn import CARequest
|
||||
|
||||
@@ -138,6 +140,36 @@ class CARequestUnitTest(unittest.TestCase):
|
||||
self.assertTrue(self.check_header(request.headers, 'User-Agent',
|
||||
'Kea-shell/1.2.3'))
|
||||
|
||||
def test_basic_http_auth(self):
|
||||
"""
|
||||
This test check if the basic HTTP authentication credential
|
||||
generated properly.
|
||||
"""
|
||||
user = 'foo'
|
||||
password = 'bar'
|
||||
buser = user.encode('latin1')
|
||||
bpassword = password.encode('latin1')
|
||||
secret = b':'.join((buser, bpassword))
|
||||
self.assertEqual(b'foo:bar', secret)
|
||||
if sys.version_info[0] == 3:
|
||||
auth = b64encode(secret).strip().decode('ascii')
|
||||
else:
|
||||
auth = b64encode(secret).strip().encode('ascii')
|
||||
self.assertEqual('Zm9vOmJhcg==', auth)
|
||||
|
||||
def test_header_auth(self):
|
||||
"""
|
||||
This test checks if the basic HTTP authentication header is
|
||||
generated properly.
|
||||
"""
|
||||
request = CARequest()
|
||||
request.auth = "Zm9vOmJhcg=="
|
||||
request.generate_headers()
|
||||
|
||||
self.assertTrue(self.check_header(request.headers,
|
||||
'Authorization',
|
||||
'Basic Zm9vOmJhcg=='))
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
This method is called after each test. Currently it does nothing.
|
||||
|
@@ -38,6 +38,7 @@ libkea_http_la_SOURCES += response_creator.cc response_creator.h
|
||||
libkea_http_la_SOURCES += response_creator_factory.h
|
||||
libkea_http_la_SOURCES += response_json.cc response_json.h
|
||||
libkea_http_la_SOURCES += url.cc url.h
|
||||
libkea_http_la_SOURCES += basic_auth.cc basic_auth.h
|
||||
|
||||
libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
|
||||
libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
|
49
src/lib/http/basic_auth.cc
Normal file
49
src/lib/http/basic_auth.cc
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <http/basic_auth.h>
|
||||
#include <util/encode/base64.h>
|
||||
#include <util/encode/utf8.h>
|
||||
|
||||
using namespace isc::util::encode;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
namespace http {
|
||||
|
||||
BasicHttpAuth::BasicHttpAuth(const std::string& user,
|
||||
const std::string& password)
|
||||
: user_(user), password_(password) {
|
||||
if (user.find(':') != string::npos) {
|
||||
isc_throw(BadValue, "user '" << user << "' must not contain a ':'");
|
||||
}
|
||||
buildSecret();
|
||||
buildCredential();
|
||||
}
|
||||
|
||||
BasicHttpAuth::BasicHttpAuth(const std::string& secret) : secret_(secret) {
|
||||
if (secret.find(':') == string::npos) {
|
||||
isc_throw(BadValue, "secret '" << secret << "' must contain a ':");
|
||||
}
|
||||
buildCredential();
|
||||
}
|
||||
|
||||
void BasicHttpAuth::buildSecret() {
|
||||
secret_ = user_ + ":" + password_;
|
||||
}
|
||||
|
||||
void BasicHttpAuth::buildCredential() {
|
||||
credential_ = encodeBase64(encodeUtf8(secret_));
|
||||
}
|
||||
|
||||
bool allow(const std::string& credential, const BasicHttpAuthList& list) {
|
||||
return (list.count(credential) != 0);
|
||||
}
|
||||
|
||||
} // end of namespace isc::http
|
||||
} // end of namespace isc
|
84
src/lib/http/basic_auth.h
Normal file
84
src/lib/http/basic_auth.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef BASIC_HTTP_AUTH_H
|
||||
#define BASIC_HTTP_AUTH_H
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace isc {
|
||||
namespace http {
|
||||
|
||||
/// @brief Represents a basic HTTP authentication.
|
||||
///
|
||||
/// It computes the credential from user and password.
|
||||
class BasicHttpAuth {
|
||||
public:
|
||||
|
||||
/// @brief Constructor.
|
||||
///
|
||||
/// @param user User name
|
||||
/// @param password Password
|
||||
/// @throw BadValue if user contains the ':' character.
|
||||
BasicHttpAuth(const std::string& user, const std::string& password);
|
||||
|
||||
/// @brief Constructor.
|
||||
///
|
||||
/// @param secret user:password string
|
||||
/// @throw BadValue if secret does not contain the ';' character.
|
||||
BasicHttpAuth(const std::string& secret);
|
||||
|
||||
/// @brief Returns the secret.
|
||||
const std::string& getSecret() const {
|
||||
return (secret_);
|
||||
}
|
||||
|
||||
/// @brief Returns the credential (base64 of the UTF-8 secret).
|
||||
const std::string& getCredential() const {
|
||||
return (credential_);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/// @brief Build the secret from user and password.
|
||||
void buildSecret();
|
||||
|
||||
/// @brief Build the credential from the secret.
|
||||
void buildCredential();
|
||||
|
||||
/// @brief User name.
|
||||
std::string user_;
|
||||
|
||||
/// @brief Password.
|
||||
std::string password_;
|
||||
|
||||
/// @brief Secret.
|
||||
std::string secret_;
|
||||
|
||||
/// @brief Credential.
|
||||
std::string credential_;
|
||||
};
|
||||
|
||||
/// @brief Type of pointers to basic HTTP authentication objects.
|
||||
typedef boost::shared_ptr<BasicHttpAuth> BasicHttpAuthPtr;
|
||||
|
||||
/// @brief Type of basic HTTP authentication credential list.
|
||||
typedef std::unordered_set<std::string> BasicHttpAuthList;
|
||||
|
||||
/// @brief Verify if a credential is authorized.
|
||||
///
|
||||
/// @param credential Credential to validate.
|
||||
/// @param list List of authorized credentials.
|
||||
/// @return True if authorized, false otherwise.
|
||||
bool allow(const std::string& credential, const BasicHttpAuthList& list);
|
||||
|
||||
} // end of namespace isc::http
|
||||
} // end of namespace isc
|
||||
|
||||
#endif // endif BASIC_HTTP_AUTH_H
|
@@ -20,7 +20,8 @@ TESTS =
|
||||
if HAVE_GTEST
|
||||
TESTS += libhttp_unittests
|
||||
|
||||
libhttp_unittests_SOURCES = connection_pool_unittests.cc
|
||||
libhttp_unittests_SOURCES = basic_auth_unittests.cc
|
||||
libhttp_unittests_SOURCES += connection_pool_unittests.cc
|
||||
libhttp_unittests_SOURCES += date_time_unittests.cc
|
||||
libhttp_unittests_SOURCES += http_header_unittests.cc
|
||||
libhttp_unittests_SOURCES += post_request_unittests.cc
|
||||
|
55
src/lib/http/tests/basic_auth_unittests.cc
Normal file
55
src/lib/http/tests/basic_auth_unittests.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <config.h>
|
||||
#include <http/basic_auth.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace isc;
|
||||
using namespace isc::http;
|
||||
|
||||
namespace {
|
||||
|
||||
// Test that user name with a colon is rejected.
|
||||
TEST(BasicHttpAuthTest, userColon) {
|
||||
BasicHttpAuthPtr ba;
|
||||
EXPECT_THROW(ba.reset(new BasicHttpAuth("foo:bar", "")), BadValue);
|
||||
}
|
||||
|
||||
// Test that secret without a colon is rejected.
|
||||
TEST(BasicHttpAuthTest, secretNoColon) {
|
||||
BasicHttpAuthPtr ba;
|
||||
EXPECT_THROW(ba.reset(new BasicHttpAuth("foo-bar")), BadValue);
|
||||
}
|
||||
|
||||
// Test that valid user and password work.
|
||||
TEST(BasicHttpAuthTest, user) {
|
||||
BasicHttpAuthPtr ba;
|
||||
EXPECT_NO_THROW(ba.reset(new BasicHttpAuth("foo", "bar")));
|
||||
ASSERT_TRUE(ba);
|
||||
EXPECT_EQ("foo:bar", ba->getSecret());
|
||||
EXPECT_EQ("Zm9vOmJhcg==", ba->getCredential());
|
||||
}
|
||||
|
||||
// Test that valid secret work.
|
||||
TEST(BasicHttpAuthTest, secret) {
|
||||
BasicHttpAuthPtr ba;
|
||||
EXPECT_NO_THROW(ba.reset(new BasicHttpAuth("foo:bar")));
|
||||
ASSERT_TRUE(ba);
|
||||
EXPECT_EQ("foo:bar", ba->getSecret());
|
||||
EXPECT_EQ("Zm9vOmJhcg==", ba->getCredential());
|
||||
}
|
||||
|
||||
// Test that secret is encoded in UTF-8.
|
||||
TEST(BasicHttpAuthTest, utf8) {
|
||||
BasicHttpAuthPtr ba;
|
||||
EXPECT_NO_THROW(ba.reset(new BasicHttpAuth("foo\n", "b\ar")));
|
||||
ASSERT_TRUE(ba);
|
||||
EXPECT_EQ("foo\n:b\ar", ba->getSecret());
|
||||
EXPECT_EQ("Zm9vCjpiB3I=", ba->getCredential());
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
Reference in New Issue
Block a user