2
0
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:
Francis Dupont
2020-07-11 01:15:06 +02:00
parent 306d488efc
commit ab45c213b7
10 changed files with 263 additions and 6 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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))

View File

@@ -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.

View File

@@ -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)

View 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
View 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

View File

@@ -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

View 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