Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
K
k8s-crt-dnldr
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
k8s
k8s-crt-dnldr
Commits
ae5ea8db
Commit
ae5ea8db
authored
Jul 22, 2020
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add pkg/rest.
parent
04ae0ce0
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1242 additions
and
1 deletion
+1242
-1
Makefile
Makefile
+2
-1
handlers.go
pkg/rest/handlers.go
+215
-0
handlers_test.go
pkg/rest/handlers_test.go
+839
-0
server.go
pkg/rest/server.go
+186
-0
No files found.
Makefile
View file @
ae5ea8db
...
@@ -35,7 +35,8 @@ build:
...
@@ -35,7 +35,8 @@ build:
check
:
build
check
:
build
golint ./pkg/crt/...
golint ./pkg/crt/...
golint ./pkg/pem/...
golint ./pkg/pem/...
go
test
-v
./pkg/crt/... ./pkg/pem/...
golint ./pkg/rest/...
go
test
-v
./pkg/crt/... ./pkg/pem/... ./pkg/rest/...
test
:
check
test
:
check
...
...
pkg/rest/handlers.go
0 → 100644
View file @
ae5ea8db
/*
* Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package
rest
import
(
"net/http"
"regexp"
"strings"
"time"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/crt"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/pem"
"github.com/sirupsen/logrus"
)
const
(
uidParam
=
"uid"
verParam
=
"version"
)
var
(
pemsRegex
=
regexp
.
MustCompile
(
"^"
+
pemsPfx
+
"/([^/]+)/([^/]+)$"
)
allowedHealthz
=
map
[
string
]
struct
{}{
http
.
MethodGet
:
struct
{}{},
http
.
MethodHead
:
struct
{}{},
}
allowedPems
=
map
[
string
]
struct
{}{
http
.
MethodGet
:
struct
{}{},
http
.
MethodHead
:
struct
{}{},
http
.
MethodDelete
:
struct
{}{},
http
.
MethodPost
:
struct
{}{},
http
.
MethodPut
:
struct
{}{},
}
)
// Date format for Common Log Format
const
common
=
"02/Jan/2006:15:04:05 -0700"
func
reqLog
(
log
*
logrus
.
Logger
,
req
*
http
.
Request
,
now
time
.
Time
,
status
,
bytes
int
,
)
{
clientIP
:=
req
.
RemoteAddr
if
colon
:=
strings
.
LastIndex
(
clientIP
,
":"
);
colon
!=
-
1
{
clientIP
=
clientIP
[
:
colon
]
}
log
.
Infof
(
"%s - - [%s]
\"
%s %s %s
\"
%d %d
\n
"
,
clientIP
,
now
.
Format
(
common
),
req
.
Method
,
req
.
RequestURI
,
req
.
Proto
,
status
,
bytes
)
}
func
errLog
(
log
*
logrus
.
Logger
,
req
*
http
.
Request
,
err
error
)
{
log
.
Errorf
(
"%s
\"
%s %s %s
\"
: %v"
,
req
.
RemoteAddr
,
req
.
Method
,
req
.
RequestURI
,
req
.
Proto
,
err
)
}
type
healthzHndlr
struct
{
log
*
logrus
.
Logger
version
string
}
func
(
h
*
healthzHndlr
)
ServeHTTP
(
resp
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
now
:=
time
.
Now
()
resp
.
Header
()
.
Set
(
"Content-Type"
,
"text/plain"
)
resp
.
Header
()
.
Set
(
"Content-Length"
,
"0"
)
resp
.
Header
()
.
Set
(
"Cache-Control"
,
"no-store"
)
status
:=
http
.
StatusTeapot
if
req
.
URL
.
Path
!=
healthzPath
{
status
=
http
.
StatusNotFound
}
else
if
_
,
ok
:=
allowedHealthz
[
req
.
Method
];
!
ok
{
status
=
http
.
StatusMethodNotAllowed
}
else
{
status
=
http
.
StatusNoContent
resp
.
Header
()
.
Del
(
"Content-Length"
)
resp
.
Header
()
.
Set
(
"Application-Version"
,
h
.
version
)
}
resp
.
WriteHeader
(
status
)
reqLog
(
h
.
log
,
req
,
now
,
status
,
0
)
}
type
pemsHndlr
struct
{
log
*
logrus
.
Logger
files
*
pem
.
Files
crtGetter
*
crt
.
Getter
}
func
(
h
*
pemsHndlr
)
allPems
(
resp
http
.
ResponseWriter
,
req
*
http
.
Request
,
now
time
.
Time
,
)
{
resp
.
Header
()
.
Set
(
"Content-Type"
,
"text/plain"
)
resp
.
Header
()
.
Set
(
"Content-Length"
,
"0"
)
resp
.
WriteHeader
(
http
.
StatusNotImplemented
)
reqLog
(
h
.
log
,
req
,
now
,
http
.
StatusNotImplemented
,
0
)
}
func
(
h
*
pemsHndlr
)
ServeHTTP
(
resp
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
now
:=
time
.
Now
()
if
req
.
URL
.
Path
==
pemsPfx
{
h
.
allPems
(
resp
,
req
,
now
)
return
}
resp
.
Header
()
.
Set
(
"Content-Type"
,
"text/plain"
)
resp
.
Header
()
.
Set
(
"Content-Length"
,
"0"
)
status
:=
http
.
StatusTeapot
bytes
:=
0
defer
reqLog
(
h
.
log
,
req
,
now
,
status
,
bytes
)
matches
:=
pemsRegex
.
FindStringSubmatch
(
req
.
URL
.
Path
)
if
matches
==
nil
{
status
=
http
.
StatusNotFound
resp
.
WriteHeader
(
status
)
return
}
if
_
,
ok
:=
allowedPems
[
req
.
Method
];
!
ok
{
status
=
http
.
StatusMethodNotAllowed
resp
.
WriteHeader
(
status
)
return
}
if
req
.
Method
==
http
.
MethodGet
||
req
.
Method
==
http
.
MethodHead
{
status
=
http
.
StatusNotImplemented
resp
.
WriteHeader
(
status
)
return
}
ns
,
name
:=
matches
[
1
],
matches
[
2
]
if
req
.
Method
==
http
.
MethodDelete
{
if
exist
,
err
:=
h
.
files
.
Delete
(
ns
,
name
);
!
exist
{
status
=
http
.
StatusNotFound
}
else
if
err
!=
nil
{
// XXX problem description in body
status
=
http
.
StatusInternalServerError
errLog
(
h
.
log
,
req
,
err
)
}
else
{
status
=
http
.
StatusNoContent
resp
.
Header
()
.
Del
(
"Content-Length"
)
}
resp
.
WriteHeader
(
status
)
return
}
uid
:=
req
.
URL
.
Query
()
.
Get
(
uidParam
)
version
:=
req
.
URL
.
Query
()
.
Get
(
verParam
)
// XXX If-Match: uid/version
// XXX If-Unmodified-Since: compare file mtime
have
:=
h
.
files
.
Have
(
ns
,
name
,
uid
,
version
)
if
have
&&
req
.
Method
==
http
.
MethodPost
{
status
=
http
.
StatusConflict
resp
.
WriteHeader
(
status
)
return
}
else
if
h
.
files
.
Check
(
ns
,
name
,
uid
,
version
)
{
status
=
http
.
StatusNoContent
resp
.
Header
()
.
Del
(
"Content-Length"
)
resp
.
WriteHeader
(
status
)
return
}
if
found
,
valid
,
err
:=
h
.
files
.
Write
(
ns
,
name
,
uid
,
version
);
!
found
{
status
=
http
.
StatusNotFound
resp
.
WriteHeader
(
status
)
return
}
else
if
!
valid
||
err
!=
nil
{
// XXX problem description in body
status
=
http
.
StatusForbidden
errLog
(
h
.
log
,
req
,
err
)
resp
.
WriteHeader
(
status
)
return
}
if
!
have
{
status
=
http
.
StatusCreated
}
else
{
status
=
http
.
StatusNoContent
resp
.
Header
()
.
Del
(
"Content-Length"
)
}
// XXX Location: /v1/pems/ns/name?uid=uid&version=version
// XXX ETag: uid/version
// XXX LastModified: file mtime
resp
.
WriteHeader
(
status
)
}
pkg/rest/handlers_test.go
0 → 100644
View file @
ae5ea8db
This diff is collapsed.
Click to expand it.
pkg/rest/server.go
0 → 100644
View file @
ae5ea8db
/*
* Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package
rest
import
(
"context"
"net"
"net/http"
"os"
"strings"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/crt"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/pem"
"github.com/sirupsen/logrus"
)
const
(
healthzPath
=
"/v1/healthz"
pemsPfx
=
"/v1/pems"
)
// Server encapsulates the HTTP server for the REST API.
type
Server
struct
{
server
http
.
Server
addr
string
gid
int
mode
int
version
string
log
*
logrus
.
Logger
files
*
pem
.
Files
crtGetter
*
crt
.
Getter
}
// NewServer creates a Server instance for the REST API.
//
// addr is the address at which the server listens, suitable for use by
// net.Listen(). If addr begins with "unix@", then the remainder of the
// strings is a Unix domain socket at which the server listens.
//
// version is a version string for the application, whose value is to
// be returned in a response header for health checks.
//
// If addr designates a Unix domain socket, then if gid and/or mode
// are >= 0, they define permissions for the newly created socket
// file. Mode sets the file mode (permissions), and gid sets the
// socket file's group ID. gid and mode are ignored if they are
// negative, or if addr does not designate a Unix domain socket.
//
// log is a logger for request and error logging. Requests are logged
// in Common Log Format.
//
// files is initialized to mamage PEM files, see pkg/pem.Files. crtGetter
// retrieves and validates Secret data, see pkg/crt.Getter.
func
NewServer
(
addr
,
version
string
,
gid
,
mode
int
,
log
*
logrus
.
Logger
,
files
*
pem
.
Files
,
crtGetter
*
crt
.
Getter
,
)
*
Server
{
srv
:=
&
Server
{
addr
:
addr
,
gid
:
-
1
,
mode
:
-
1
,
version
:
version
,
log
:
log
,
files
:
files
,
crtGetter
:
crtGetter
,
}
if
gid
>=
0
{
srv
.
gid
=
gid
}
if
mode
>=
0
{
srv
.
mode
=
mode
}
return
srv
}
// Start launches an HTTP server for the REST API, listening at the
// address given in NewServer().
//
// Start may return a non-nil error if there is an error opening the
// listenner address. It may also return an error setting permissions,
// if the listen address is a Unix domain sockert.
//
// Otherwise, the server is started in a separate goroutine, and Start
// returns immediately.
func
(
srv
*
Server
)
Start
()
error
{
srv
.
log
.
Infof
(
"Starting HTTP server for the REST API"
)
network
:=
"tcp"
if
strings
.
HasPrefix
(
srv
.
addr
,
"unix@"
)
{
srv
.
addr
=
(
srv
.
addr
)[
5
:
]
if
_
,
err
:=
os
.
Stat
(
srv
.
addr
);
err
==
nil
{
srv
.
log
.
Info
(
"deleting "
,
srv
.
addr
)
if
err
=
os
.
Remove
(
srv
.
addr
);
err
!=
nil
{
srv
.
log
.
Error
(
"Cannot delete "
,
srv
.
addr
,
": "
,
err
)
return
err
}
}
else
if
!
os
.
IsNotExist
(
err
)
{
return
err
}
network
=
"unix"
}
lsnr
,
err
:=
net
.
Listen
(
network
,
srv
.
addr
)
if
err
!=
nil
{
return
err
}
if
network
==
"unix"
{
if
srv
.
gid
!=
-
1
{
if
err
=
os
.
Chown
(
srv
.
addr
,
-
1
,
srv
.
gid
);
err
!=
nil
{
srv
.
log
.
Errorf
(
"Cannot set gid %d for %s: %v"
,
srv
.
gid
,
srv
.
addr
,
err
)
return
err
}
}
if
srv
.
mode
!=
-
1
{
if
err
=
os
.
Chmod
(
srv
.
addr
,
os
.
FileMode
(
srv
.
mode
));
err
!=
nil
{
srv
.
log
.
Errorf
(
"Cannot set mode %0o for %s: "
+
"%v"
,
srv
.
mode
,
srv
.
addr
,
err
)
return
err
}
}
}
srv
.
log
.
Infof
(
"REST API HTTP server listening at %s"
,
srv
.
addr
)
mux
:=
http
.
NewServeMux
()
mux
.
Handle
(
healthzPath
,
&
healthzHndlr
{
log
:
srv
.
log
,
version
:
srv
.
version
,
})
mux
.
Handle
(
pemsPfx
,
&
pemsHndlr
{
log
:
srv
.
log
,
files
:
srv
.
files
,
crtGetter
:
srv
.
crtGetter
,
})
srv
.
server
=
http
.
Server
{
Handler
:
mux
}
go
func
()
{
if
err
:=
srv
.
server
.
Serve
(
lsnr
);
err
!=
nil
{
if
err
!=
http
.
ErrServerClosed
{
srv
.
log
.
Errorf
(
"REST API HTTP server: %+v"
,
err
)
}
}
}()
return
nil
}
// Stop shuts down the HTTP server running the REST API.
func
(
srv
*
Server
)
Stop
()
error
{
srv
.
log
.
Info
(
"Stopping HTTP server for the REST API"
)
ctx
:=
context
.
Background
()
if
err
:=
srv
.
server
.
Shutdown
(
ctx
);
err
!=
nil
{
return
err
}
return
nil
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment