Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118306338
vyos-http-api-server
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
vyos-http-api-server
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2019 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
import
os
import
sys
import
grp
import
json
import
traceback
import
threading
import
vyos.config
import
bottle
from
functools
import
wraps
from
vyos.configsession
import
ConfigSession
,
ConfigSessionError
from
vyos.config
import
VyOSError
DEFAULT_CONFIG_FILE
=
'/etc/vyos/http-api.conf'
CFG_GROUP
=
'vyattacfg'
app
=
bottle
.
default_app
()
# Giant lock!
lock
=
threading
.
Lock
()
def
load_server_config
():
with
open
(
DEFAULT_CONFIG_FILE
)
as
f
:
config
=
json
.
load
(
f
)
return
config
def
check_auth
(
key_list
,
key
):
id
=
None
for
k
in
key_list
:
if
k
[
'key'
]
==
key
:
id
=
k
[
'id'
]
return
id
def
error
(
code
,
msg
):
bottle
.
response
.
status
=
code
resp
=
{
"success"
:
False
,
"error"
:
msg
,
"data"
:
None
}
return
json
.
dumps
(
resp
)
def
success
(
data
):
resp
=
{
"success"
:
True
,
"data"
:
data
,
"error"
:
None
}
return
json
.
dumps
(
resp
)
def
auth_required
(
f
):
@wraps
(
f
)
def
decorated_function
(
*
args
,
**
kwargs
):
key
=
bottle
.
request
.
forms
.
get
(
"key"
)
api_keys
=
app
.
config
[
'vyos_keys'
]
id
=
check_auth
(
api_keys
,
key
)
if
not
id
:
return
error
(
401
,
"Valid API key is required"
)
return
f
(
*
args
,
**
kwargs
)
return
decorated_function
@app
.
route
(
'/configure'
,
method
=
'POST'
)
@auth_required
def
configure
():
session
=
app
.
config
[
'vyos_session'
]
env
=
session
.
get_session_env
()
config
=
vyos
.
config
.
Config
(
session_env
=
env
)
strict_field
=
bottle
.
request
.
forms
.
get
(
"strict"
)
if
strict_field
==
"true"
:
strict
=
True
else
:
strict
=
False
commands
=
bottle
.
request
.
forms
.
get
(
"data"
)
if
not
commands
:
return
error
(
400
,
"Non-empty data field is required"
)
else
:
try
:
commands
=
json
.
loads
(
commands
)
except
Exception
as
e
:
return
error
(
400
,
"Failed to parse JSON:
{0}
"
.
format
(
e
))
# Allow users to pass just one command
if
not
isinstance
(
commands
,
list
):
commands
=
[
commands
]
# We don't want multiple people/apps to be able to commit at once,
# or modify the shared session while someone else is doing the same,
# so the lock is really global
lock
.
acquire
()
status
=
200
error_msg
=
None
try
:
for
c
in
commands
:
# What we've got may not even be a dict
if
not
isinstance
(
c
,
dict
):
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
: any command must be a dict"
.
format
(
json
.
dumps
(
c
)))
# Missing op or path is a show stopper
if
not
(
'op'
in
c
):
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
: missing
\"
op
\"
field"
.
format
(
json
.
dumps
(
c
)))
if
not
(
'path'
in
c
):
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
: missing
\"
path
\"
field"
.
format
(
json
.
dumps
(
c
)))
# Missing value is fine, substitute for empty string
if
'value'
in
c
:
value
=
c
[
'value'
]
else
:
value
=
""
op
=
c
[
'op'
]
path
=
c
[
'path'
]
if
not
path
:
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
: empty path"
.
format
(
json
.
dumps
(
c
)))
# Type checking
if
not
isinstance
(
path
,
list
):
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
:
\"
path
\"
field must be a list"
.
format
(
json
.
dumps
(
c
)))
if
not
isinstance
(
value
,
str
):
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
:
\"
value
\"
field must be a string"
.
format
(
json
.
dumps
(
c
)))
# Account for the case when value field is present and set to null
if
not
value
:
value
=
""
# For vyos.configsessios calls that have no separate value arguments,
# and for type checking too
try
:
cfg_path
=
" "
.
join
(
path
+
[
value
])
.
strip
()
except
TypeError
:
raise
ConfigSessionError
(
"Malformed command
\"
{0}
\"
:
\"
path
\"
field must be a list of strings"
.
format
(
json
.
dumps
(
c
)))
if
op
==
'set'
:
# XXX: it would be nice to do a strict check for "path already exists",
# but there's probably no way to do that
session
.
set
(
path
,
value
=
value
)
elif
op
==
'delete'
:
if
strict
and
not
config
.
exists
(
cfg_path
):
raise
ConfigSessionError
(
"Cannot delete [
{0}
]: path/value does not exist"
.
format
(
cfg_path
))
session
.
delete
(
path
,
value
=
value
)
elif
op
==
'comment'
:
session
.
comment
(
path
,
value
=
value
)
else
:
raise
ConfigSessionError
(
"
\"
{0}
\"
is not a valid operation"
.
format
(
op
))
# end for
session
.
commit
()
print
(
"Configuration modified via HTTP API using key
\"
{0}
\"
"
.
format
(
id
))
except
ConfigSessionError
as
e
:
session
.
discard
()
status
=
400
if
app
.
config
[
'vyos_debug'
]:
print
(
traceback
.
format_exc
(),
file
=
sys
.
stderr
)
error_msg
=
str
(
e
)
except
Exception
as
e
:
session
.
discard
()
print
(
traceback
.
format_exc
(),
file
=
sys
.
stderr
)
status
=
500
# Don't give the details away to the outer world
error_msg
=
"An internal error occured. Check the logs for details."
finally
:
lock
.
release
()
if
status
!=
200
:
return
error
(
status
,
error_msg
)
else
:
return
success
(
None
)
@app
.
route
(
'/retrieve'
,
method
=
'POST'
)
@auth_required
def
get_value
():
session
=
app
.
config
[
'vyos_session'
]
env
=
session
.
get_session_env
()
config
=
vyos
.
config
.
Config
(
session_env
=
env
)
command
=
bottle
.
request
.
forms
.
get
(
"data"
)
command
=
json
.
loads
(
command
)
try
:
op
=
command
[
'op'
]
path
=
" "
.
join
(
command
[
'path'
])
except
KeyError
:
return
error
(
400
,
"Missing required field.
\"
op
\"
and
\"
path
\"
fields are required"
)
try
:
if
op
==
'returnValue'
:
res
=
config
.
return_value
(
path
)
elif
op
==
'returnValues'
:
res
=
config
.
return_values
(
path
)
elif
op
==
'exists'
:
res
=
config
.
exists
(
path
)
elif
op
==
'showConfig'
:
config_format
=
'json'
if
'configFormat'
in
command
:
config_format
=
command
[
'configFormat'
]
res
=
session
.
show_config
(
path
=
command
[
'path'
])
if
config_format
==
'json'
:
config_tree
=
vyos
.
configtree
.
ConfigTree
(
res
)
res
=
json
.
loads
(
config_tree
.
to_json
())
elif
config_format
==
'json_ast'
:
config_tree
=
vyos
.
configtree
.
ConfigTree
(
res
)
res
=
json
.
loads
(
config_tree
.
to_json_ast
())
elif
config_format
==
'raw'
:
pass
else
:
return
error
(
400
,
"
\"
{0}
\"
is not a valid config format"
.
format
(
config_format
))
else
:
return
error
(
400
,
"
\"
{0}
\"
is not a valid operation"
.
format
(
op
))
except
VyOSError
as
e
:
return
error
(
400
,
str
(
e
))
except
Exception
as
e
:
print
(
traceback
.
format_exc
(),
file
=
sys
.
stderr
)
return
error
(
500
,
"An internal error occured. Check the logs for details."
)
return
success
(
res
)
@app
.
route
(
'/config-file'
,
method
=
'POST'
)
@auth_required
def
config_file_op
():
session
=
app
.
config
[
'vyos_session'
]
command
=
bottle
.
request
.
forms
.
get
(
"data"
)
command
=
json
.
loads
(
command
)
try
:
op
=
command
[
'op'
]
except
KeyError
:
return
error
(
400
,
"Missing required field
\"
op
\"
"
)
try
:
if
op
==
'save'
:
try
:
path
=
command
[
'file'
]
except
KeyError
:
path
=
'/config/config.boot'
res
=
session
.
save_config
(
path
)
elif
op
==
'load'
:
try
:
path
=
command
[
'file'
]
except
KeyError
:
return
error
(
400
,
"Missing required field
\"
file
\"
"
)
res
=
session
.
load_config
(
path
)
res
=
session
.
commit
()
else
:
return
error
(
400
,
"
\"
{0}
\"
is not a valid operation"
.
format
(
op
))
except
VyOSError
as
e
:
return
error
(
400
,
str
(
e
))
except
Exception
as
e
:
print
(
traceback
.
format_exc
(),
file
=
sys
.
stderr
)
return
error
(
500
,
"An internal error occured. Check the logs for details."
)
return
success
(
res
)
@app
.
route
(
'/image'
,
method
=
'POST'
)
@auth_required
def
config_file_op
():
session
=
app
.
config
[
'vyos_session'
]
command
=
bottle
.
request
.
forms
.
get
(
"data"
)
command
=
json
.
loads
(
command
)
try
:
op
=
command
[
'op'
]
except
KeyError
:
return
error
(
400
,
"Missing required field
\"
op
\"
"
)
try
:
if
op
==
'add'
:
try
:
url
=
command
[
'url'
]
except
KeyError
:
return
error
(
400
,
"Missing required field
\"
url
\"
"
)
res
=
session
.
install_image
(
url
)
elif
op
==
'delete'
:
try
:
name
=
command
[
'name'
]
except
KeyError
:
return
error
(
400
,
"Missing required field
\"
name
\"
"
)
res
=
session
.
remove_image
(
name
)
else
:
return
error
(
400
,
"
\"
{0}
\"
is not a valid operation"
.
format
(
op
))
except
VyOSError
as
e
:
return
error
(
400
,
str
(
e
))
except
Exception
as
e
:
print
(
traceback
.
format_exc
(),
file
=
sys
.
stderr
)
return
error
(
500
,
"An internal error occured. Check the logs for details."
)
return
success
(
res
)
if
__name__
==
'__main__'
:
# systemd's user and group options don't work, do it by hand here,
# else no one else will be able to commit
cfg_group
=
grp
.
getgrnam
(
CFG_GROUP
)
os
.
setgid
(
cfg_group
.
gr_gid
)
# Need to set file permissions to 775 too so that every vyattacfg group member
# has write access to the running config
os
.
umask
(
0o002
)
try
:
server_config
=
load_server_config
()
except
Exception
as
e
:
print
(
"Failed to load the HTTP API server config:
{0}
"
.
format
(
e
))
session
=
ConfigSession
(
os
.
getpid
())
app
.
config
[
'vyos_session'
]
=
session
app
.
config
[
'vyos_keys'
]
=
server_config
[
'api_keys'
]
app
.
config
[
'vyos_debug'
]
=
server_config
[
'debug'
]
bottle
.
run
(
app
,
host
=
server_config
[
"listen_address"
],
port
=
server_config
[
"port"
],
debug
=
True
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:14 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4374696
Default Alt Text
vyos-http-api-server (10 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment