Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: Support HTTP basic authentication for API. v6.0.4, v5.0.152 #3458

Merged
merged 11 commits into from
Apr 1, 2023
93 changes: 93 additions & 0 deletions trunk/3rdparty/srs-bench/blackbox/http_api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// The MIT License (MIT)
//
// # Copyright (c) 2023 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package blackbox

import (
"context"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"net/http"
"sync"
"testing"
"time"
)

func TestFast_Http_Api_Basic_Auth(t *testing.T) {
// This case is run in parallel.
t.Parallel()

// Setup the max timeout for this case.
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()

// Check a set of errors.
var r0, r1, r2, r3, r4, r5, r6 error
defer func(ctx context.Context) {
if err := filterTestError(ctx.Err(), r0, r1, r2, r3, r4, r5, r6); err != nil {
t.Errorf("Fail for err %+v", err)
} else {
logger.Tf(ctx, "test done with err %+v", err)
}
}(ctx)

var wg sync.WaitGroup
defer wg.Wait()

// Start SRS server and wait for it to be ready.
svr := NewSRSServer(func(v *srsServer) {
v.envs = []string{
"SRS_HTTP_API_AUTH_ENABLED=on",
"SRS_HTTP_API_AUTH_USERNAME=admin",
"SRS_HTTP_API_AUTH_PASSWORD=admin",
}
})
wg.Add(1)
go func() {
defer wg.Done()
r0 = svr.Run(ctx, cancel)
}()

<-svr.ReadyCtx().Done()

if true {
defer cancel()

var res *http.Response
url := fmt.Sprintf("http://admin:admin@localhost:%v/api/v1/versions", svr.APIPort())
res, r1 = http.Get(url)
if r1 == nil && res.StatusCode != 200 {
r2 = errors.Errorf("get status code=%v, expect=200", res.StatusCode)
}

url = fmt.Sprintf("http://admin:123456@localhost:%v/api/v1/versions", svr.APIPort())
res, r3 = http.Get(url)
if r3 == nil && res.StatusCode != 401 {
r4 = errors.Errorf("get status code=%v, expect=401", res.StatusCode)
}

url = fmt.Sprintf("http://localhost:%v/api/v1/versions", svr.APIPort())
res, r5 = http.Get(url)
if r5 == nil && res.StatusCode != 401 {
r6 = errors.Errorf("get status code=%v, expect=401", res.StatusCode)
}
}
}
36 changes: 21 additions & 15 deletions trunk/3rdparty/srs-bench/blackbox/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func (v *backendService) Run(ctx context.Context, cancel context.CancelFunc) err
}

select {
case <- ctx.Done():
case <-ctx.Done():
case <-time.After(v.duration):
logger.Tf(ctx, "Process killed duration=%v, pid=%v, name=%v, args=%v", v.duration, v.pid, v.name, v.args)
cmd.Process.Kill()
Expand Down Expand Up @@ -418,6 +418,8 @@ type SRSServer interface {
RTMPPort() int
// HTTPPort is the HTTP stream port.
HTTPPort() int
// APIPort is the HTTP API port.
APIPort() int
// SRTPort is the SRT UDP port.
SRTPort() int
}
Expand Down Expand Up @@ -510,6 +512,10 @@ func (v *srsServer) HTTPPort() int {
return v.httpListen
}

func (v *srsServer) APIPort() int {
return v.apiListen
}

func (v *srsServer) SRTPort() int {
return v.srtListen
}
Expand All @@ -524,7 +530,7 @@ func (v *srsServer) Run(ctx context.Context, cancel context.CancelFunc) error {
)

// Create directories.
if err := os.MkdirAll(path.Join(v.workDir, "./objs/nginx/html"), os.FileMode(0755) | os.ModeDir); err != nil {
if err := os.MkdirAll(path.Join(v.workDir, "./objs/nginx/html"), os.FileMode(0755)|os.ModeDir); err != nil {
return errors.Wrapf(err, "SRS create directory %v", path.Join(v.workDir, "./objs/nginx/html"))
}

Expand Down Expand Up @@ -657,7 +663,7 @@ type ffmpegClient struct {

func NewFFmpeg(opts ...func(v *ffmpegClient)) FFmpegClient {
v := &ffmpegClient{
process: newBackendService(),
process: newBackendService(),
cancelCaseWhenQuit: true,
}

Expand Down Expand Up @@ -701,7 +707,7 @@ func (v *ffmpegClient) Run(ctx context.Context, cancel context.CancelFunc) error
ffCtx, ffCancel := context.WithCancel(ctx)
go func() {
select {
case <- ctx.Done():
case <-ctx.Done():
case <-ffCtx.Done():
if v.cancelCaseWhenQuit {
cancel()
Expand Down Expand Up @@ -1144,17 +1150,17 @@ func (v *HooksEventBase) HookAction() string {

type HooksEventOnDvr struct {
HooksEventBase
Stream string `json:"stream"`
Stream string `json:"stream"`
StreamUrl string `json:"stream_url"`
StreamID string `json:"stream_id"`
CWD string `json:"cwd"`
File string `json:"file"`
TcUrl string `json:"tcUrl"`
App string `json:"app"`
Vhost string `json:"vhost"`
IP string `json:"ip"`
ClientIP string `json:"client_id"`
ServerID string `json:"server_id"`
StreamID string `json:"stream_id"`
CWD string `json:"cwd"`
File string `json:"file"`
TcUrl string `json:"tcUrl"`
App string `json:"app"`
Vhost string `json:"vhost"`
IP string `json:"ip"`
ClientIP string `json:"client_id"`
ServerID string `json:"server_id"`
}

type HooksService interface {
Expand All @@ -1171,7 +1177,7 @@ type hooksService struct {
httpPort int
dispose func()

r0 error
r0 error
hooksOnDvr chan HooksEvent
}

Expand Down
13 changes: 13 additions & 0 deletions trunk/conf/full.conf
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,19 @@ http_api {
# Always off by https://github.com/ossrs/srs/issues/2653
#allow_update off;
}
# the auth is authentication for http api
auth {
# whether enable the HTTP AUTH.
# Overwrite by env SRS_HTTP_API_AUTH_ENABLED
# default: off
enabled on;
# The username of Basic authentication:
# Overwrite by env SRS_HTTP_API_AUTH_USERNAME
username admin;
# The password of Basic authentication:
# Overwrite by env SRS_HTTP_API_AUTH_PASSWORD
password admin;
}
# For https_api or HTTPS API.
https {
# Whether enable HTTPS API.
Expand Down
83 changes: 82 additions & 1 deletion trunk/src/app/srs_app_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2284,7 +2284,7 @@ srs_error_t SrsConfig::check_normal_config()
for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
SrsConfDirective* obj = conf->at(i);
string n = obj->name;
if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api" && n != "https") {
if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api" && n != "auth" && n != "https") {
return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal http_api.%s", n.c_str());
}

Expand All @@ -2296,6 +2296,15 @@ srs_error_t SrsConfig::check_normal_config()
}
}
}

if (n == "auth") {
for (int j = 0; j < (int)obj->directives.size(); j++) {
string m = obj->at(j)->name;
if (m != "enabled" && m != "username" && m != "password") {
return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal http_api.auth.%s", m.c_str());
}
}
}
}
}
if (true) {
Expand Down Expand Up @@ -7605,6 +7614,78 @@ bool SrsConfig::get_raw_api_allow_update()
return false;
}

bool SrsConfig::get_http_api_auth_enabled()
{
SRS_OVERWRITE_BY_ENV_BOOL("srs.http_api.auth.enabled"); // SRS_HTTP_API_AUTH_ENABLED

static bool DEFAULT = false;

SrsConfDirective* conf = root->get("http_api");
if (!conf) {
return DEFAULT;
}

conf = conf->get("auth");
if (!conf) {
return DEFAULT;
}

conf = conf->get("enabled");
if (!conf || conf->arg0().empty()) {
return DEFAULT;
}

return SRS_CONF_PERFER_FALSE(conf->arg0());
}

std::string SrsConfig::get_http_api_auth_username()
{
SRS_OVERWRITE_BY_ENV_STRING("srs.http_api.auth.username"); // SRS_HTTP_API_AUTH_USERNAME

static string DEFAULT = "";

SrsConfDirective* conf = root->get("http_api");
if (!conf) {
return DEFAULT;
}

conf = conf->get("auth");
if (!conf) {
return DEFAULT;
}

conf = conf->get("username");
if (!conf) {
return DEFAULT;
}

return conf->arg0();
}

std::string SrsConfig::get_http_api_auth_password()
{
SRS_OVERWRITE_BY_ENV_STRING("srs.http_api.auth.password"); // SRS_HTTP_API_AUTH_PASSWORD

static string DEFAULT = "";

SrsConfDirective* conf = root->get("http_api");
if (!conf) {
return DEFAULT;
}

conf = conf->get("auth");
if (!conf) {
return DEFAULT;
}

conf = conf->get("password");
if (!conf) {
return DEFAULT;
}

return conf->arg0();
}

SrsConfDirective* SrsConfig::get_https_api()
{
SrsConfDirective* conf = root->get("http_api");
Expand Down
6 changes: 6 additions & 0 deletions trunk/src/app/srs_app_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,12 @@ class SrsConfig
virtual bool get_raw_api_allow_query();
// Whether allow rpc update.
virtual bool get_raw_api_allow_update();
// Whether http api auth enabled.
virtual bool get_http_api_auth_enabled();
// Get the http api auth username.
virtual std::string get_http_api_auth_username();
// Get the http api auth password.
virtual std::string get_http_api_auth_password();
// https api section
private:
SrsConfDirective* get_https_api();
Expand Down
33 changes: 27 additions & 6 deletions trunk/src/app/srs_app_http_conn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ ISrsHttpConnOwner::~ISrsHttpConnOwner()
SrsHttpConn::SrsHttpConn(ISrsHttpConnOwner* handler, ISrsProtocolReadWriter* fd, ISrsHttpServeMux* m, string cip, int cport)
{
parser = new SrsHttpParser();
cors = new SrsHttpCorsMux();
auth = new SrsHttpAuthMux(m);
cors = new SrsHttpCorsMux(auth);

http_mux = m;
handler_ = handler;

Expand All @@ -74,6 +76,7 @@ SrsHttpConn::~SrsHttpConn()

srs_freep(parser);
srs_freep(cors);
srs_freep(auth);

srs_freep(delta_);
}
Expand Down Expand Up @@ -227,10 +230,10 @@ srs_error_t SrsHttpConn::process_request(ISrsHttpResponseWriter* w, ISrsHttpMess

srs_trace("HTTP #%d %s:%d %s %s, content-length=%" PRId64 "", rid, ip.c_str(), port,
r->method_str().c_str(), r->url().c_str(), r->content_length());
// use cors server mux to serve http request, which will proxy to http_remux.

// proxy to cors-->auth-->http_remux.
if ((err = cors->serve_http(w, r)) != srs_success) {
return srs_error_wrap(err, "mux serve");
return srs_error_wrap(err, "cors serve");
}

return err;
Expand All @@ -256,14 +259,27 @@ srs_error_t SrsHttpConn::set_crossdomain_enabled(bool v)
{
srs_error_t err = srs_success;

// initialize the cors, which will proxy to mux.
if ((err = cors->initialize(http_mux, v)) != srs_success) {
if ((err = cors->initialize(v)) != srs_success) {
return srs_error_wrap(err, "init cors");
}

return err;
}

srs_error_t SrsHttpConn::set_auth_enabled(bool auth_enabled)
{
srs_error_t err = srs_success;

// initialize the auth, which will proxy to mux.
if ((err = auth->initialize(auth_enabled,
_srs_config->get_http_api_auth_username(),
_srs_config->get_http_api_auth_password())) != srs_success) {
return srs_error_wrap(err, "init auth");
}

return err;
}

srs_error_t SrsHttpConn::set_jsonp(bool v)
{
parser->set_jsonp(v);
Expand Down Expand Up @@ -451,6 +467,11 @@ srs_error_t SrsHttpxConn::start()
return srs_error_wrap(err, "set cors=%d", v);
}

bool auth_enabled = _srs_config->get_http_api_auth_enabled();
if ((err = conn->set_auth_enabled(auth_enabled)) != srs_success) {
return srs_error_wrap(err, "set auth");
}

return conn->start();
}

Expand Down
Loading